Skip to content

fix(cli): report an async command error as one line, not a stack trace - #1271

Merged
SaulMoro merged 1 commit into
Tencent:mainfrom
Smilewithoutfalling:fix/cli-command-error-exit
Oct 22, 2026
Merged

SaulMoro merged 1 commit into
Tencent:mainfrom
Smilewithoutfalling:fix/cli-command-error-exit

Conversation

@Smilewithoutfalling

Copy link
Copy Markdown
Contributor

Summary

The entry boundary reported only NotInitializedError as one line. Any other error an async action threw escaped as an unhandled rejection, so Node printed it under a cli.js:NNNN header with the whole stack (teamai session list outside an initialized project, for example). The boundary now reports whatever a command action throws as one line on stderr and exits 1, and keeps the stack for --verbose and ~/.teamai/debug.log.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Test Plan

  • npx tsc --noEmit passes
  • npm run lint passes on the touched files (0 warnings, 0 errors)
  • npx vitest run passes on every entry test this touches; the full local suite is left to CI (it takes over 15 minutes on this host)
  • Added/updated tests for the change
  • Real CLI, after npm run build, in a cold $HOME (see the record below)

Real CLI

Before (main d60e68f5, built the same way), teamai session list in an empty cwd:

file:///.../dist/cli.js:112960
    throw new Error(
          ^

Error: No teamai project config found. Run `teamai init` or pass --repo-root. Session sync will not use the current working directory.
    at resolveRepoRoot (file:///.../dist/cli.js:112960:11)
    at async _Command.<anonymous> (file:///.../dist/cli.js:113708:22)
    at async _Command.parseAsync (file:///.../dist/cli.js:1902:9)
[exit=1]

After:

$ teamai session list
stdout: (empty)
stderr: x No teamai project config found. Run `teamai init` or pass --repo-root. Session sync will not use the current working directory.
[exit=1]

$ teamai -v session list
stdout:   [debug] Error: No teamai project config found. ...
            at resolveRepoRoot (...)
stderr: x No teamai project config found. ...
[exit=1]

~/.teamai/debug.log carries both the [ERROR] line and the [DEBUG] stack in either mode.

Unchanged: status, env list, members list, tags list still print one line and exit 1; --version still exits 0.

The new src/__tests__/e2e/cli-error-exit.test.ts fails on the build from main (2 failed: the stack is in stderr, and --verbose prints nothing) and passes on this one.

Suite note

The entry tests (index-not-initialized, index-migration-trigger, index-codebase-help, index-import-help, cli-path, session-cmd) run as one batch. One of them fails on this Windows host, and it fails the same way with this change reverted:

session-cmd.test.ts > session migrate --json > names a Claude Code session whose first lines do not parse unreadable, not not_found

The test drives a --cwd /run/dir fixture whose POSIX path does not resolve here, so it is not from this PR. The full suite is left to CI.

Related Issues

Fixes #1270

Notes for Reviewers

  • The stack goes to stdout under --verbose. That is where log.debug already writes (writeInfoLine), and log.debug also appends to ~/.teamai/debug.log on every run, so support keeps the trace without a flag. I did not special-case a second destination for it.
  • reportNotInitialized stays as it is. status, list and push still report inside their own action; the boundary is what covers the commands that never wrapped themselves, such as session list.
  • Two existing help tests changed. index-codebase-help.test.ts and index-import-help.test.ts mock process.exit to throw and asserted that the entry rejects with it (rejects.toThrow('process.exit(0)')). A boundary that reports every error now reports that throw instead, so they await the import and reset process.exitCode. The help text they check is already on stdout by then, and in production --help / --version never reach the boundary: commander calls the real process.exit.
  • index-not-initialized.test.ts gained four cases for reportCommandError itself: message and exit code without rejecting, the stack reaching log.debug, an empty message falling back to the error name, and a thrown non-Error value with no invented stack.
  • Seen but left alone: teamai digest prints Failed to generate digest: teamai is not initialized... and exits 0, so a script cannot tell it failed. Different cause, so I did not fold it in here.

The entry boundary reported only `NotInitializedError` as one line; every
other error an async action threw escaped as an unhandled rejection, so Node
printed it under a `cli.js:NNNN` header with the whole stack. `teamai session
list` outside an initialized project showed exactly that.

`reportCommandError` now reports whatever a command action throws: one line
through `log.error`, `process.exitCode = 1`, and the stack through
`log.debug`, which always appends to ~/.teamai/debug.log and prints on the
console under --verbose.

The per-command `reportNotInitialized` wrapper is unchanged; the boundary is
what covers the commands that never wrapped themselves.

Two help tests asserted that the entry rejects when `process.exit` is mocked.
It reports that throw now, so they await the import and reset exitCode; the
help text they check is already on stdout. In production commander calls the
real `process.exit` for --help, so the boundary never sees it.

Fixes Tencent#1270.
@github-actions

Copy link
Copy Markdown

No findings in the specified diff.

The PR description satisfies the testing requirement: it documents a post-build real-CLI run, including exit codes, stderr output, and verbose stack output. Deferring the full suite to CI is not blocking.

Review was read-only; I did not run, build, or install anything from the PR.

@SaulMoro SaulMoro left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This was generated by AI during triage.

@Smilewithoutfalling Approving. We built this head and main (d60e68f5) and ran both in an isolated HOME.

  • teamai session list outside a project: main exits 1 with Node's cli.js header and the full stack. This head exits 1 with one line on stderr, ✖ No teamai project config found....
  • teamai -v session list prints the same line, and the stack shows up as debug output. ~/.teamai/debug.log gets the error and the stack with or without -v.
  • teamai status, --version and an unknown command behave the same as on main.
  • The new e2e test passes on this head and fails 2/2 against main's build.

[P3 nit] In src/__tests__/index-codebase-help.test.ts:23 and src/__tests__/index-import-help.test.ts:23, the old rejects.toThrow('process.exit(0)') also checked that --help reaches process.exit(0). The new form no longer checks it. Keeping the spy and adding expect(exitSpy).toHaveBeenCalledWith(0) would bring that back. Optional.

#1234, #1208 and #1191 also edit src/index.ts, but none of them touches the entry error handler.

@SaulMoro
SaulMoro merged commit 6bcdaf3 into Tencent:main Oct 22, 2026
17 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.

fix(cli): an error thrown by an async command prints a Node stack trace

3 participants