Skip to content

feat(cli-v2): unified Rust-style error renderer + --debug + stderr fixes#16040

Open
iamnamananand996 wants to merge 4 commits into
mainfrom
devin/1779374495-cli-v2-error-renderer
Open

feat(cli-v2): unified Rust-style error renderer + --debug + stderr fixes#16040
iamnamananand996 wants to merge 4 commits into
mainfrom
devin/1779374495-cli-v2-error-renderer

Conversation

@iamnamananand996
Copy link
Copy Markdown
Contributor

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-v2 has three different "render an error" code paths (the handleError boundary in withContext.ts, plus two yargs .fail handlers in cli.ts and commandGroup.ts), each emitting a different visual style, none of them surfacing CliError.docsLink even though that field exists. This PR collapses them into one shared renderer modeled on the (already great) MdxParseError output, so every failure now looks like:

error[AUTH_ERROR]: You are not logged in to Fern.

hint: Run `fern auth login`, or set the FERN_TOKEN environment variable.
see:  https://buildwithfern.com/learn/cli/auth

For typos:

error[USER_ERROR]: Did you mean whoami?

Authenticate fern
  fern auth login   Log in to Fern
  fern auth logout  Log out of Fern
  ...

Changes Made

  • Add packages/cli/cli-v2/src/errors/renderError.ts — a single renderer that produces the Rust-style error[CODE]: title / hint: / see: envelope. Handles CliError, ValidationError, SourcedValidationError, KeyringUnavailableError, generic Error, and non-Error throwables (which previously crashed the boundary).
  • Extend CliError with a new optional hint field (and keep docsLink), wired through CliError.authRequired() / CliError.unauthorized(). docsLink is now actually rendered to the user.
  • Refactor handleError in withContext.ts to use the renderer and call printLogFilePath on every failure — previously only SIGINT and TaskGroup paths surfaced the per-run debug log path.
  • Add packages/cli/cli-v2/src/commands/_internal/yargsFailHandler.ts — a shared makeYargsFailHandler({ showHelp }) that both cli.ts (top-level) and commandGroup.ts (subcommand groups) now use. The two yargs .fail handlers were drift-prone copies that bypassed the renderer entirely.
  • Enable yargs recommendCommands() on the root parser and every command group so typos suggest the closest command (e.g. fern auth whoamiiDid you mean whoami?).
  • Add a top-level --debug flag and FERN_DEBUG=1 env var on GlobalArgs. When on, the renderer appends the full stack trace and the entire error.cause chain (for any error class, not just Error).
  • Add packages/cli/cli-v2/src/errors/printViolations.ts with formatViolations() / formatIssues() and migrate check and sdk generate to use it (replacing copy-pasted formatting code).
  • Fix three stdout-vs-stderr bugs:
    • fern auth whoami no longer prints the "not logged in" warning to stdout — it now throws a CliError with hint, so the error boundary renders it on stderr. JSON consumers still get { user: null, loggedIn: false } on stdout when --json is passed.
    • fern auth status routes non-JSON "not logged in" guidance to context.stderr (was context.stdout).
    • KeyringUnavailableError now 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

  • Unit tests added: packages/cli/cli-v2/src/__test__/renderError.test.ts (11 tests covering TaskAbortSignal, CliError with/without message, multi-line detail, ValidationError, SourcedValidationError with line/col, KeyringUnavailableError, generic Error, non-Error throwables, debug-mode stack+cause).
  • Unit tests added: packages/cli/cli-v2/src/__test__/printViolations.test.ts (5 tests covering empty input, line+col formatting, line-only formatting, issue formatting via ValidationIssue).
  • Full cli-v2 test suite passes locally (714 tests).
  • pnpm format, pnpm lint:biome, pnpm check all green.
  • Manual end-to-end smoke against the production CLI build (pnpm fern:buildnode dist/prod/cli.cjs beta ...):
    • fern beta auth whoami (not logged in): renders error[AUTH_ERROR]: You are not logged in to Fern. + hint: Run \fern auth login`...` on stderr, exit 1.
    • fern beta auth whoamii (typo): renders error[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 bare Error: 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:

  • exit-code mapping (CliError.Code → distinct exit codes for shell scripts) — behavior change, wants its own review.
  • --json error envelope { ok, code, message, hint, docsLink, violations, logFile }.
  • A small well-known-error template registry mirroring MdxErrorCode.
  • The user-facing CLI errors README.

Link to Devin session: https://app.devin.ai/sessions/c00fe336eaa44387a47db9083451e93c
Requested by: @iamnamananand996

- 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
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@iamnamananand996 iamnamananand996 marked this pull request as ready for review May 21, 2026 14:47
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

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.

fern-support and others added 3 commits May 21, 2026 22:00
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants