fix: improve CLI error messages for unknown subcommands#431
Merged
Conversation
Add a note to CLAUDE.md directing contributors to follow the skill-creator skill when creating or updating swamp-* skills. This prevents skills from drifting in structure and quality over time as different authors make changes without a shared standard.
This is a merge commit the virtual branches in your workspace. Due to GitButler managing multiple virtual branches, you cannot switch back and forth between git branches and virtual branches easily. If you switch to another branch, GitButler will need to be reinitialized. If you commit on this branch, GitButler will throw it away. Here are the branches that are currently applied: - docs/skill-creator-guideline (refs/gitbutler/docs/skill-creator-guideline) branch head: b965d87 For more information about what we're doing here, check out our docs: https://docs.gitbutler.com/features/branch-management/integration-branch
When a user types `swamp model hetzner-server method getSystemInfo`,
Cliffy sees `hetzner-server` as an unknown subcommand and unhelpfully
suggests "Did you mean command 'create'?" — which has nothing to do
with what the user intended.
This change intercepts Cliffy's unknown command errors and replaces
them with context-aware suggestions that teach the correct syntax:
error: "hetzner-server" is not a subcommand of "model".
Did you mean one of these?
swamp model get hetzner-server
swamp model method run hetzner-server <method>
swamp model validate hetzner-server
For typos (e.g. `swamp model creat`), we still show a "Did you mean?"
suggestion using Levenshtein distance, but point to the correct command
rather than Cliffy's generic guess.
## What changed
- Extract shared `levenshteinDistance` and `findClosestMatch` from two
files that had identical copies into `src/domain/string_distance.ts`
- Add `unknownCommandErrorHandler` that intercepts Cliffy's
`ValidationError` for unknown commands and builds context-specific
suggestions for model, model method, workflow, and vault commands
- Attach the error handler to the root CLI command plus `modelCommand`,
`modelMethodCommand`, `workflowCommand`, and `vaultCommand` — needed
because Cliffy's `getErrorHandler()` only checks one parent level up
- Add `.example()` calls to `modelMethodRunCommand` for discoverability
## Plan vs implementation
The original plan called for adding `.error()` only on the root command
in `mod.ts`. During implementation we discovered that Cliffy's
`getErrorHandler()` only traverses one parent level — so errors thrown
by `model method` (depth 3) never reached the root handler. We added
the handler to each key subcommand (`model`, `model method`, `workflow`,
`vault`) to ensure coverage. This added 3 files beyond the plan:
`model_create.ts`, `workflow.ts`, and `vault.ts`.
The plan also suggested throwing `UserError` from the handler, but
Cliffy's error flow requires calling `Deno.exit(2)` to prevent its
default handler from also printing — so the handler prints via
`console.error` and exits directly.
Closes #374
Co-authored-by: Walter Heck <walterheck@helixiora.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review: Approved ✓
No Blocking Issues
This PR improves the CLI user experience by replacing Cliffy's generic error messages with context-aware suggestions. The implementation is clean and well-tested.
Review Checklist
| Requirement | Status |
|---|---|
TypeScript strict mode, no any types |
✓ No new any types introduced |
| Named exports | ✓ All new files use named exports |
| AGPLv3 license headers | ✓ Present on all new .ts files |
| Unit tests adjacent to source | ✓ string_distance_test.ts and unknown_command_handler_test.ts |
| Test coverage | ✓ 20 tests across new modules |
| Security | ✓ No vulnerabilities - string parsing only |
| DDD principles | ✓ Utilities in domain/, CLI infra in cli/ |
Code Quality Highlights
- Good refactoring: Extracts duplicated Levenshtein distance code from
schema_path_validator.tsandinput_override_validation_service.tsinto sharedstring_distance.ts - Proper error handling: Uses
ValidationErrorinstance check before processing, re-throws non-matching errors - Well-typed: Functions have explicit parameter and return types
- Clear documentation: JSDoc comments explain the purpose of each function
Suggestions (non-blocking)
- The
CLAUDE.mdchange adding the Skills section is unrelated to the PR's stated purpose (fixing unknown subcommand errors). Consider splitting unrelated doc changes into separate PRs for cleaner history. This is minor and doesn't block merge.
DDD Assessment
The placement of code follows appropriate domain boundaries:
string_distance.tsindomain/is appropriate as a shared utility supporting domain validationunknown_command_handler.tsincli/is appropriate as CLI infrastructure code
LGTM! 🚀
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #374
When a user types
swamp model hetzner-server method getSystemInfo, Cliffy seeshetzner-serveras an unknown subcommand and outputs:This is actively misleading — suggesting
createwhen the user clearly meant to operate on a model. This PR intercepts Cliffy's unknown command errors and replaces them with context-aware suggestions that teach the correct syntax.Before
After
Typos still get targeted suggestions:
Why this approach
modelsuggestsget/method run/validate,workflowsuggestsget/run/validate,vaultsuggestsget/put/list-keys.What changed
src/domain/string_distance.tsfindClosestMatch(extracted from 2 files with identical copies)src/domain/string_distance_test.tssrc/cli/unknown_command_handler.tssrc/cli/unknown_command_handler_test.tssrc/cli/mod.ts.error(unknownCommandErrorHandler)to root commandsrc/cli/commands/model_create.ts.error()tomodelCommandsrc/cli/commands/model_method_run.ts.error()tomodelMethodCommand+.example()callssrc/cli/commands/workflow.ts.error()toworkflowCommandsrc/cli/commands/vault.ts.error()tovaultCommandsrc/domain/expressions/schema_path_validator.tsstring_distance.tssrc/domain/inputs/input_override_validation_service.tsstring_distance.tsPlan vs implementation
The original plan called for adding
.error()only on the root command inmod.ts. During implementation we discovered that Cliffy'sgetErrorHandler()only traverses one parent level — so errors thrown bymodel method(depth 3) never reached the root handler. We added the handler to each key subcommand (model,model method,workflow,vault) to ensure full coverage. This added 3 files beyond the plan (model_create.ts,workflow.ts,vault.ts).The plan also suggested throwing
UserErrorfrom the handler, but Cliffy's error flow requires callingDeno.exit(2)to prevent its default handler from also printing — so the handler prints viaconsole.errorand exits directly.Test plan
swamp model hetzner-server method getSystemInfo— shows model context suggestionsswamp model creat my-type my-name— shows typo suggestion forcreateswamp model method hetzner-server getSystemInfo— shows method context suggestionswamp workflow deploy-app— shows workflow context suggestionsswamp vault my-vault— shows vault context suggestionsdeno check— passesdeno lint— passesdeno fmt— passesdeno run test— 1836 tests pass, 0 failuresdeno run compile— binary compiles successfully🤖 Generated with Claude Code