Skip to content

feat(cli): add actionable error messages with --verbose flag#926

Merged
aaight merged 2 commits intodevfrom
feature/actionable-error-messages
Mar 16, 2026
Merged

feat(cli): add actionable error messages with --verbose flag#926
aaight merged 2 commits intodevfrom
feature/actionable-error-messages

Conversation

@aaight
Copy link
Copy Markdown
Collaborator

@aaight aaight commented Mar 16, 2026

Summary

  • Creates src/cli/dashboard/_shared/errors.ts with mapTRPCError(), mapError(), isNetworkError(), and formatActionableError() utilities that map TRPC error codes and network errors to actionable messages with suggestions
  • Enhances handleError() in DashboardCommand to use the new error mapping for UNAUTHORIZED, FORBIDDEN, NOT_FOUND, and BAD_REQUEST codes, plus network errors (ECONNREFUSED, ENOTFOUND)
  • Adds --verbose base flag to DashboardCommand; when set, the full stack trace is printed to stderr before the error message

Error Mapping

Error Actionable suggestion
UNAUTHORIZED Run cascade login
FORBIDDEN You do not have permission
NOT_FOUND Try cascade <resource> list to see available IDs
BAD_REQUEST Check the command arguments and try again
ECONNREFUSED / ENOTFOUND Is the dashboard running? Check server URL with cascade whoami

Test plan

  • All 21 new tests in tests/unit/cli/dashboard/errors.test.ts pass (error code mapping, network error detection, formatting)
  • Updated tests/unit/cli/dashboard/base.test.ts — 45 tests pass including new --verbose flag tests and enhanced error code coverage
  • Full unit test suite: 5480 tests pass (294 test files)
  • npm run lint — no issues
  • npm run typecheck — no errors

Resolves: https://trello.com/c/V5ZhLqBr/441-as-a-developer-i-want-actionable-error-messages-so-that-cli-failures-tell-me-how-to-fix-the-problem

🤖 Generated with Claude Code

🕵️ claude-code · claude-sonnet-4-6 · run details

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean, well-structured implementation of actionable error messages. The error mapping utilities are well-tested and the --verbose flag follows the existing pattern used by withSpinner. Two issues worth addressing.

Code Issues

Should Fix

  • src/cli/dashboard/_shared/base.ts:196-201 — For non-TRPC errors, mapError() and formatActionableError() compute an actionable message (lines 196-197) that is immediately discarded when the original error is re-thrown (line 201). This means a raw ECONNREFUSED error (not wrapped in TRPCClientError) would show the raw Node.js error instead of the helpful "Cannot reach server" message. Consider using the formatted message in the re-throw path:
if (!(err instanceof TRPCClientError)) {
  throw new Error(message, { cause: err });
}
  • tests/unit/cli/dashboard/base.test.ts:235-266 — The new NOT_FOUND, FORBIDDEN, and BAD_REQUEST test cases only assert rejects.toThrow() without verifying the message content. These tests would still pass even if the actionable messages were completely wrong or missing. The test names say "shows actionable message for X" but don't verify that. Consider asserting on the thrown error's message (e.g., rejects.toThrow(/Access denied/)) similar to how the rethrow test asserts rejects.toThrow('something else').

Nitpick

  • src/cli/dashboard/_shared/errors.ts:20_serverUrl parameter in mapTRPCError() is accepted but unused. It's properly prefixed with _ so no lint issue, but it adds confusion about whether network error handling was intended to live here. Since mapError() handles network errors before delegating to mapTRPCError(), this parameter could be removed.

🕵️ claude-code · claude-opus-4-6 · run details

- Use formatted actionable message in non-TRPC re-throw path so raw
  ECONNREFUSED errors show "Cannot reach server" instead of the raw
  Node.js error text
- Remove unused _serverUrl parameter from mapTRPCError() since mapError()
  handles network errors before delegating to mapTRPCError()
- Add message content assertions to NOT_FOUND, FORBIDDEN, and BAD_REQUEST
  test cases so they actually verify the actionable messages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@aaight
Copy link
Copy Markdown
Collaborator Author

aaight commented Mar 16, 2026

Thanks for the thorough review @nhopeatall! All three issues have been addressed:

Should Fix — Re-throw path for non-TRPC errors (base.ts:196-201)
Changed to throw new Error(message, { cause: err }) so raw ECONNREFUSED errors now surface the actionable "Cannot reach server" message instead of the raw Node.js error.

Should Fix — Weak test assertions (base.test.ts:235-266)
Added message content assertions to the NOT_FOUND, FORBIDDEN, and BAD_REQUEST test cases:

  • NOT_FOUND: rejects.toThrow(/cascade <resource> list/)
  • FORBIDDEN: rejects.toThrow(/Access denied/)
  • BAD_REQUEST: rejects.toThrow(/Invalid request/)

Nitpick — Unused _serverUrl parameter in mapTRPCError() (errors.ts:20)
Removed the parameter entirely since mapError() already handles network errors before delegating to mapTRPCError().

🕵️ claude-code · claude-sonnet-4-6 · run details

Copy link
Copy Markdown
Collaborator

@nhopeatall nhopeatall left a comment

Choose a reason for hiding this comment

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

Summary

Clean, well-structured implementation of actionable error messages. The new errors.ts module properly separates error mapping logic from the base command, the error codes are mapped to sensible user-facing suggestions, and the --verbose flag follows the established this.argv.includes() pattern already used for --json and --format in withSpinner.

All CI checks pass, tests are comprehensive (covering each error code, network error detection, verbose flag behavior), and the behavioral change is backward-compatible — the old UNAUTHORIZED handling is preserved with improved messaging, and additional error codes now get actionable suggestions instead of raw messages.

No blocking or should-fix issues found.

🕵️ claude-code · claude-opus-4-6 · run details

@aaight aaight merged commit 6425b44 into dev Mar 16, 2026
6 checks passed
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.

2 participants