feat(cli-v2): unified Rust-style error renderer + --debug + stderr fixes#16040
Open
iamnamananand996 wants to merge 4 commits into
Open
feat(cli-v2): unified Rust-style error renderer + --debug + stderr fixes#16040iamnamananand996 wants to merge 4 commits into
iamnamananand996 wants to merge 4 commits into
Conversation
- Add a single renderError() that produces error[CODE]: title / hint: / see: envelopes for every cli-v2 failure - Render docsLink (previously stored but ignored) and a new optional CliError.hint field - Extend CliError with hint metadata - Print the per-run debug log path on every failure (not just SIGINT / TaskGroup) - Route auth status / auth whoami / KeyringUnavailableError output through stderr so | jq pipelines stay clean - Add a global --debug flag and FERN_DEBUG=1 env var that show full stack traces and error.cause chains - Dedupe the two yargs .fail handlers (cli.ts + commandGroup.ts) into a single helper that goes through the same renderer - Enable yargs recommendCommands() so typos suggest the closest command - Add formatViolations() / formatIssues() helpers and migrate check + sdk generate to use them - Tests for renderError + printViolations
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
This was referenced May 21, 2026
Remove the unused FormatViolationOptions interface from printViolations.ts. Update renderError to use a simplified titleForCode(code) (no fallback), add an INTERNAL_ERROR title, and use assertNever in the default branch to enforce exhaustive handling of CliError codes. This tightens error title selection and eliminates implicit fallback titles.
Resolved import conflict in withContext.ts: - Keep renderError import (wave-1) - Add maybeNagForUpgrade import (from main) - Drop unused error-class imports that wave-1's unified renderer obsoleted.
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.
Description
Linear ticket: Refs FER-10016
First low-risk wave of the cli-v2 error-handling overhaul tracked in FER-10016. The full analysis is in the linked issue; this PR is purely UX/DX cleanup — no exit-code or JSON-envelope behavior changes, those will follow in a separate PR.
The single biggest UX problem today is that
cli-v2has three different "render an error" code paths (thehandleErrorboundary inwithContext.ts, plus two yargs.failhandlers incli.tsandcommandGroup.ts), each emitting a different visual style, none of them surfacingCliError.docsLinkeven though that field exists. This PR collapses them into one shared renderer modeled on the (already great)MdxParseErroroutput, so every failure now looks like:For typos:
Changes Made
packages/cli/cli-v2/src/errors/renderError.ts— a single renderer that produces the Rust-styleerror[CODE]: title/hint:/see:envelope. HandlesCliError,ValidationError,SourcedValidationError,KeyringUnavailableError, genericError, and non-Errorthrowables (which previously crashed the boundary).CliErrorwith a new optionalhintfield (and keepdocsLink), wired throughCliError.authRequired()/CliError.unauthorized().docsLinkis now actually rendered to the user.handleErrorinwithContext.tsto use the renderer and callprintLogFilePathon every failure — previously only SIGINT and TaskGroup paths surfaced the per-run debug log path.packages/cli/cli-v2/src/commands/_internal/yargsFailHandler.ts— a sharedmakeYargsFailHandler({ showHelp })that bothcli.ts(top-level) andcommandGroup.ts(subcommand groups) now use. The two yargs.failhandlers were drift-prone copies that bypassed the renderer entirely.recommendCommands()on the root parser and every command group so typos suggest the closest command (e.g.fern auth whoamii→Did you mean whoami?).--debugflag andFERN_DEBUG=1env var onGlobalArgs. When on, the renderer appends the full stack trace and the entireerror.causechain (for any error class, not justError).packages/cli/cli-v2/src/errors/printViolations.tswithformatViolations()/formatIssues()and migratecheckandsdk generateto use it (replacing copy-pasted formatting code).fern auth whoamino longer prints the "not logged in" warning to stdout — it now throws aCliErrorwithhint, so the error boundary renders it on stderr. JSON consumers still get{ user: null, loggedIn: false }on stdout when--jsonis passed.fern auth statusroutes non-JSON "not logged in" guidance tocontext.stderr(wascontext.stdout).KeyringUnavailableErrornow flows through the unified renderer to stderr.Updated README.md generator(n/a — no user-facing CLI README change in this PR; per FER-10016 the README rewrite is a separate follow-up)Testing
packages/cli/cli-v2/src/__test__/renderError.test.ts(11 tests coveringTaskAbortSignal,CliErrorwith/without message, multi-line detail,ValidationError,SourcedValidationErrorwith line/col,KeyringUnavailableError, genericError, non-Errorthrowables, debug-mode stack+cause).packages/cli/cli-v2/src/__test__/printViolations.test.ts(5 tests covering empty input, line+col formatting, line-only formatting, issue formatting viaValidationIssue).pnpm format,pnpm lint:biome,pnpm checkall green.pnpm fern:build→node dist/prod/cli.cjs beta ...):fern beta auth whoami(not logged in): renderserror[AUTH_ERROR]: You are not logged in to Fern.+hint: Run \fern auth login`...` on stderr, exit 1.fern beta auth whoamii(typo): renderserror[USER_ERROR]: Did you mean whoami?followed by the auth group help, exit 1.fern beta --debug auth whoami: appends the full stack trace under the envelope.fern beta check(no fern.yml): renders the unified envelope (no more bareError:prefix).Followups (intentionally deferred to keep this PR low-risk)
Tracked under FER-10016 and detailed in the analysis doc on the issue. Next PRs will cover:
CliError.Code→ distinct exit codes for shell scripts) — behavior change, wants its own review.--jsonerror envelope{ ok, code, message, hint, docsLink, violations, logFile }.MdxErrorCode.Link to Devin session: https://app.devin.ai/sessions/c00fe336eaa44387a47db9083451e93c
Requested by: @iamnamananand996