fix(cli): suppress Cliffy Command dump on ValidationError (swamp-club#171)#1229
fix(cli): suppress Cliffy Command dump on ValidationError (swamp-club#171)#1229
Conversation
…#171)
When the CLI hit a Cliffy ValidationError (unknown flag, unknown option,
missing required arg), renderError fell through to logger.fatal('{error}',
err), which dumped the entire Cliffy Command tree (~300 lines, with circular
refs) via Deno.inspect. The useful "did you mean" hint was buried.
Recognize ValidationError at the rendering boundary and treat it like
UserError — log just the message, never the full object. Replaces the
fragile isCliffyMissingArgError string-match helper with an instanceof
check; the string match is now dead code (Cliffy throws ValidationError
for missing-arg cases too).
Before: 310 lines for `swamp workflow run namespace-debug --inputs foo=bar`
After: 1 line — `Error: 'Unknown option "--inputs". Did you mean option "--input"?'`
Closes swamp-club#171
There was a problem hiding this comment.
CLI UX Review
Blocking
None.
Suggestions
None.
Verdict
PASS — pure UX improvement with no regressions.
This PR fixes a real and severe error-rendering bug. When Cliffy throws a ValidationError for an unknown flag, unknown option, or missing required argument, the old code fell through to logger.fatal("{error}", err), which dumped the entire Cliffy Command tree via Deno.inspect (~310 lines, including circular refs). The actionable message (e.g., Did you mean option "--input"?) was buried on line 1.
After this change, ValidationError is treated identically to UserError at the presentation boundary: only err.message is logged, never the object. The interaction with unknownCommandErrorHandler is correctly preserved — that handler already exits for "Unknown command" errors before they reach renderError; all other ValidationErrors re-throw and now render cleanly.
Log mode: 310 lines → 1 clean line with the Cliffy hint visible.
JSON mode: stack trace suppressed for validation errors (correct — stack traces on typos are noise for pipe consumers).
The replacement of the fragile startsWith("Missing argument(s):") string match with instanceof ValidationError is strictly more correct and covers the full class of Cliffy validation failures.
There was a problem hiding this comment.
Code Review
Clean, well-scoped bug fix. The change correctly replaces a fragile string-match helper (isCliffyMissingArgError) with a proper instanceof ValidationError check at the presentation boundary. This handles all Cliffy validation failures (unknown options, missing args, etc.) rather than just the one string pattern that was previously matched.
No Blocking Issues
Suggestions
- Minor: The test comments are slightly verbose relative to the project's "default to no comments" convention (e.g., lines 105–106, 119–120 in the test file), but given these are regression tests for a specific issue, the context is useful for future readers. Not worth changing.
What looks good
- DDD alignment: The fix stays cleanly in the presentation layer — framework errors (
ValidationError) are caught at the rendering boundary without leaking into or from the domain layer.UserErrorremains the domain-level error concept. - Import boundary: No libswamp internal path violations.
@cliffy/commandis already used elsewhere in the codebase (e.g.,unknown_command_handler.ts). - Test coverage: Three new/updated tests cover the exact regression (circular-ref Command dump in both log and JSON modes) plus the existing missing-arg case migrated to
ValidationError. - Security: The fix improves the security posture by preventing internal framework details from leaking to end users.
- Minimal blast radius: Only two files changed, dead code removed, no unrelated refactoring.
Summary
When the CLI hit a Cliffy
ValidationError(unknown flag, unknown option, missing required arg),renderErrorfell through tologger.fatal("{error}", err), which dumped the entire CliffyCommandtree (~300 lines, with circular refs) viaDeno.inspect. The useful "did you mean" hint was buried on line 1.This recognizes
ValidationErrorat the presentation boundary and treats it the same asUserError— log just the message, never the full object. Replaces the fragileisCliffyMissingArgErrorstring-match helper with aninstanceof ValidationErrorcheck; the string match is now dead code (Cliffy throwsValidationErrorfor missing-arg cases too).Before / after
swamp workflow run namespace-debug --inputs podModel=fooOther paths verified to stay clean:
unknownCommandErrorHandler)--jsonmode (still emits structured{error}JSON, no leaked internals)Test plan
deno checkpassesdeno lintpassesdeno fmtpassesdeno run test— 4928 passed, 0 faileddeno run compileproduces a working binary--jsonmode) remain cleanerror_output_test.tssynthesize a Cliffy-shapedValidationErrorwith a circularcmdpayload and assert thatCommand {,settings:, and[Circularnever leak into rendered output (covers both log and json mode)Closes swamp-club#171