Skip to content

Comments

fix: improve CLI error messages for unknown subcommands#431

Merged
stack72 merged 3 commits intomainfrom
fix/unknown-subcommand-error-messages
Feb 23, 2026
Merged

fix: improve CLI error messages for unknown subcommands#431
stack72 merged 3 commits intomainfrom
fix/unknown-subcommand-error-messages

Conversation

@stack72
Copy link
Contributor

@stack72 stack72 commented Feb 23, 2026

Summary

Fixes #374

When a user types swamp model hetzner-server method getSystemInfo, Cliffy sees hetzner-server as an unknown subcommand and outputs:

error: Unknown command "hetzner-server". Did you mean command "create"?

This is actively misleading — suggesting create when 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

$ swamp model hetzner-server method getSystemInfo
error: Unknown command "hetzner-server". Did you mean command "create"?

After

$ swamp model hetzner-server method getSystemInfo
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

  Run "swamp model --help" for available subcommands.

Typos still get targeted suggestions:

$ swamp model creat my-type my-name
error: Unknown command "creat". Did you mean "create"?

  Run "swamp model --help" for available subcommands.

Why this approach

  • No arg rewriting — we don't silently guess what the user meant and run it. We teach the correct syntax and let them decide.
  • Context-aware — suggestions differ by command: model suggests get/method run/validate, workflow suggests get/run/validate, vault suggests get/put/list-keys.
  • Typo detection — uses Levenshtein distance (extracted from existing duplicated code) to detect close misspellings and suggest the right subcommand.

What changed

File Change
src/domain/string_distance.ts NEW — Shared Levenshtein distance + findClosestMatch (extracted from 2 files with identical copies)
src/domain/string_distance_test.ts NEW — 10 tests
src/cli/unknown_command_handler.ts NEW — Error interceptor with context-aware suggestions
src/cli/unknown_command_handler_test.ts NEW — 10 tests
src/cli/mod.ts Added .error(unknownCommandErrorHandler) to root command
src/cli/commands/model_create.ts Added .error() to modelCommand
src/cli/commands/model_method_run.ts Added .error() to modelMethodCommand + .example() calls
src/cli/commands/workflow.ts Added .error() to workflowCommand
src/cli/commands/vault.ts Added .error() to vaultCommand
src/domain/expressions/schema_path_validator.ts Imports from shared string_distance.ts
src/domain/inputs/input_override_validation_service.ts Imports from shared string_distance.ts

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 full coverage. This added 3 files beyond the plan (model_create.ts, workflow.ts, 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.

Test plan

  • swamp model hetzner-server method getSystemInfo — shows model context suggestions
  • swamp model creat my-type my-name — shows typo suggestion for create
  • swamp model method hetzner-server getSystemInfo — shows method context suggestion
  • swamp workflow deploy-app — shows workflow context suggestions
  • swamp vault my-vault — shows vault context suggestions
  • deno check — passes
  • deno lint — passes
  • deno fmt — passes
  • deno run test — 1836 tests pass, 0 failures
  • deno run compile — binary compiles successfully

🤖 Generated with Claude Code

stack72 and others added 3 commits February 23, 2026 14:59
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>
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts and input_override_validation_service.ts into shared string_distance.ts
  • Proper error handling: Uses ValidationError instance 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)

  1. The CLAUDE.md change 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.ts in domain/ is appropriate as a shared utility supporting domain validation
  • unknown_command_handler.ts in cli/ is appropriate as CLI infrastructure code

LGTM! 🚀

@stack72 stack72 merged commit 8b1ecf2 into main Feb 23, 2026
4 checks passed
@stack72 stack72 deleted the fix/unknown-subcommand-error-messages branch February 23, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

command like feedback on incorrect syntax is not super helpful

2 participants