Skip to content

fix(cli): exit non-zero from whoami and login when unauthenticated - #3654

Merged
kojiwakayama merged 2 commits into
mainfrom
fix/whoami-exit-code
Aug 13, 2026
Merged

fix(cli): exit non-zero from whoami and login when unauthenticated#3654
kojiwakayama merged 2 commits into
mainfrom
fix/whoami-exit-code

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The lie

veryfront whoami prints ✗ Not logged in and exits 0.

Reproduced against the published npm artifact veryfront@0.1.1232, in a sandbox outside the monorepo, with the exit code captured directly (no pipe, which would mask it):

$ env -u VERYFRONT_API_TOKEN ./node_modules/.bin/veryfront whoami

  ✗ Not logged in
  Run 'veryfront login' to authenticate
EXIT=0

$ env -u VERYFRONT_API_TOKEN ./node_modules/.bin/veryfront whoami --json
{
  "success": true,
  "command": "whoami",
  "data": { "authenticated": false }
}
EXIT=0

The exit code is the machine-readable contract. Any CI step, shell script, or agent that gates on veryfront whoami believes it is authenticated when it is not, then fails later somewhere unrelated and much harder to diagnose. A human used veryfront whoami exactly this way — to confirm auth before starting a task — during the dogfood session that found this.

Sibling check

Swept the neighbouring commands for the same class of defect:

Command Unauthenticated / failing behaviour Verdict
whoami prints "Not logged in", exits 0 bug — fixed here
login (non-interactive, no token) prints "Not logged in. Set VERYFRONT_API_TOKEN…", exits 0 bug — fixed here
logout exits 0 correct
doctor a fail check throws and exits 1; warnings exit 0 unless --strict correct
routes (no route dirs) exits 0 — nothing failed correct
unknown command exits 1 / 2 in JSON mode correct

login is the same shape as whoami and lives in the same router entry group and auth module, so it is fixed here. doctor's warning-exits-0 behaviour is deliberate and opt-in via --strict; left alone.

Cause

cli/router.ts awaited the auth functions and discarded their return values:

"whoami": async () => async () => {
  const { whoami } = await import("./auth/index.ts");
  await whoami();          // returns AuthIdentity | null — null was dropped
},

whoami() already reports "no usable credential" by returning null. That signal simply never reached the process exit code. Same for login().

Fix

Both handlers now exit 1 when no credential was obtained.

Exit code: 1, not 2. The CLI output style guide's exit-code table reserves 2 for invalid usage (wrong args, missing required) and 1 for a general error; 130 is interrupt. veryfront whoami with no credential is well-formed usage that answers "no", so it is 1. This also matches the precedent of tools like grep and git diff --quiet, where 1 means "the answer is no" rather than "you called me wrong".

Human-readable output is unchanged — it still names the problem plainly and points at veryfront login. --json still emits the authenticated: false envelope, now alongside the non-zero exit. Both whoami and login help text now document the exit codes.

Tests

cli/auth/exit-code.integration.test.ts drives the real CLI entry point in a subprocess and asserts on the process exit code itself — the thing that was wrong. Each run gets a throwaway XDG_CONFIG_HOME and a temp cwd so neither a developer's stored token nor a repository .env can leak in.

Red, before the fix:

running 1 test from ./cli/auth/exit-code.integration.test.ts
cli/auth exit codes ...
  whoami exits non-zero when no credential is available ... FAILED
error: AssertionError: Values are not equal.
    [Diff] Actual / Expected
-   0
+   1

  whoami --json exits non-zero and still reports authenticated: false ... FAILED
  login exits non-zero when it cannot obtain a credential ... FAILED

FAILED | 0 passed | 1 failed (3 steps) (6s)

Green, after:

running 1 test from ./cli/auth/exit-code.integration.test.ts
cli/auth exit codes ...
  whoami exits non-zero when no credential is available ... ok (2s)
  whoami --json exits non-zero and still reports authenticated: false ... ok (2s)
  login exits non-zero when it cannot obtain a credential ... ok (2s)
  whoami still exits zero when a credential validates ... ok (2s)

ok | 1 passed (4 steps) | 0 failed (8s)

The fourth case points the CLI at a stub /me server and asserts exit 0 plus the identity in the output, so the fix cannot degenerate into always exiting 1.

Also run clean: deno fmt --check, deno lint, deno check on the changed files, and deno test cli/router.test.ts cli/auth/ cli/help/ (17 passed, 226 steps).

Summary by CodeRabbit

  • New Features
    • Added clear exit-code behavior for authentication commands.
    • login returns exit code 1 when authentication fails.
    • whoami returns exit code 1 when no valid credentials are available and 0 when authentication succeeds.
  • Documentation
    • Updated command help text to explain authentication-related exit codes.

`veryfront whoami` printed "✗ Not logged in" and exited 0. The exit code is
the machine-readable contract: CI steps, shell scripts, and agents gate on
`veryfront whoami` to confirm auth, believe they are authenticated, and then
fail later somewhere unrelated and much harder to diagnose.

The router awaited `whoami()` and discarded its return value, so the null
"no usable credential" result never reached the process exit code. `login`
had the same shape — it returns null when it cannot obtain a credential and
the router dropped that too.

Both now exit 1. This repo's CLI reserves 2 for invalid usage (see the exit
code table in the CLI output style guide), so "no usable credential" is a
general failure, code 1. The human-readable output is unchanged: it still
names the problem and points at `veryfront login`. `--json` still emits the
`authenticated: false` envelope, now alongside a non-zero exit.

Covered by subprocess tests that drive the real CLI entry point and assert on
the process exit code itself, including a positive case behind a stub API so
the fix cannot degenerate into always exiting 1.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aabc1855-7e73-4575-93ac-cdb454174d1c

📥 Commits

Reviewing files that changed from the base of the PR and between 3fe4ab8 and 2037c5f.

📒 Files selected for processing (2)
  • cli/auth/exit-code.integration.test.ts
  • cli/router.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a1686b03-0200-4efe-b9e9-2ed3dc54cab3

📥 Commits

Reviewing files that changed from the base of the PR and between 06d303a and 3fe4ab8.

📒 Files selected for processing (4)
  • cli/auth/exit-code.integration.test.ts
  • cli/commands/login/command-help.ts
  • cli/commands/whoami/command-help.ts
  • cli/router.ts

📝 Walkthrough

Walkthrough

The CLI now returns status 1 for failed login and unauthenticated whoami commands. Help text documents these statuses. Integration tests verify failed and successful authentication scenarios.

Changes

Authentication exit statuses

Layer / File(s) Summary
Router exit behavior
cli/router.ts
login and whoami now exit with status 1 when authentication fails. Successful authentication retains status 0.
Help text and integration coverage
cli/commands/login/command-help.ts, cli/commands/whoami/command-help.ts, cli/auth/exit-code.integration.test.ts
Help text documents the exit statuses. Subprocess integration tests cover unauthenticated, failed login, JSON, and successful authenticated scenarios.

Estimated code review effort: 2 (Simple) | ~10 minutes

Mergeability Score: ⚪ Minimal · up to 3fe4a

This change makes unauthenticated whoami and login commands return exit code 1 while preserving their output and successful authenticated behavior. No actionable merge-blocking risk remains after normal checks and review.

Suggested reviewers: kwakayama

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: unauthenticated whoami and login commands now exit with a non-zero status.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/whoami-exit-code

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3fe4ab85b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cli/commands/login/command-help.ts
… opt-outs

Two follow-ups on the same contract.

`login --provider anthropic|openai` had the exact defect this PR set out to
fix: both provider functions return Promise<boolean> and the router discarded
the result before reaching the exit guard, so an empty key, an invalid key, or
a validation network error still exited 0 — contradicting the help text this
PR added. Both branches now exit 1, so every login shape reports failure the
same way.

The new integration suite also carried sanitizeOps/sanitizeResources: false,
which pushed the sanitizer ratchet from 404 to 406 and failed lint. The suite
leaks nothing — every subprocess is awaited to completion, the stub server is
shut down, and the temp dirs are removed — so the opt-outs are deleted rather
than the baseline raised. The suite passes with both sanitizers enabled.

The provider branches are deliberately not covered by a subprocess test:
promptPassword calls Deno.stdin.setRaw(), which throws ENODEV on a non-TTY
stdin, so such a test would exit 1 from that crash rather than from the
failure path and would pass with this fix reverted. Noted in the test file.
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 13, 2026
Merged via the queue into main with commit ac521d2 Aug 13, 2026
33 checks passed
@kojiwakayama
kojiwakayama deleted the fix/whoami-exit-code branch August 13, 2026 06:58
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.

1 participant