Skip to content

fix(claude-code): parse auth status JSON before exit code; use authMethod (#1260)#1302

Merged
muddlebee merged 2 commits intoTracer-Cloud:mainfrom
Davidson3556:fix-claude-auth-cases-a-c
May 5, 2026
Merged

fix(claude-code): parse auth status JSON before exit code; use authMethod (#1260)#1302
muddlebee merged 2 commits intoTracer-Cloud:mainfrom
Davidson3556:fix-claude-auth-cases-a-c

Conversation

@Davidson3556
Copy link
Copy Markdown
Contributor

Fixes #1260 (follow-up to #1295)

Describe the changes you have made in this PR -

Two bugs in _probe_cli_auth surfaced by running the live claude auth status against every documented auth state. Both still on main after #1295 merged.

1. No auth + no env key — wizard says "could not verify" instead of "requires login"
The CLI exits 1 with valid JSON {\"loggedIn\": false, \"authMethod\": \"none\", ...} when no auth is configured. The probe short-circuited on returncode != 0 before parsing JSON and returned None, so the wizard took the logged_in is None branch ("could not verify") instead of the False branch ("requires login"). Users had no signal that they needed to run claude login — the same logged_in=None UX surface that #1199 originally highlighted.

2. Subscription + ANTHROPIC_API_KEY in env — detail string misreports source
The CLI emits both authMethod: \"claude.ai\" and apiKeySource: \"ANTHROPIC_API_KEY\" when a subscription user also has the env var set, but the active method is the subscription. The prior code branched on apiKeySource-presence and reported "Authenticated via ANTHROPIC_API_KEY". authMethod (claude.ai / api_key / none) is the authoritative discriminator; apiKeySource is set whenever the env contributes a key, regardless of which method is active.

Implementation

  • Extracted _auth_status_from_json_payload and _try_parse_auth_status_stdout so the JSON path is testable in isolation.
  • _probe_cli_auth now parses stdout first; the exit-code error path is a fallback only when JSON is absent or unparseable.
  • Detail-string branching uses authMethod first; falls back to apiKeySource / email for older CLI versions that omit authMethod.

Demo / verification

Live against claude 2.1.123 across all four auth states:

State Before After
Subscription only (True, \"...subscription (email)\") unchanged ✅
API key only (True, \"...ANTHROPIC_API_KEY\") unchanged ✅
Subscription + env API key (True, \"...ANTHROPIC_API_KEY\") (True, \"...subscription (email)\")
No auth (fresh HOME) (None, \"claude auth status failed: ...\") (False, \"Not authenticated. ...\")

Reproduce the no-auth case in isolation (doesn't touch your real auth):

```bash
env -i HOME=/tmp/empty PATH=$PATH NO_COLOR=1 claude auth status; echo "exit: $?"

{"loggedIn": false, "authMethod": "none", "apiProvider": "firstParty"}

exit: 1

```

Tests

  • make lint
  • make format-check
  • make typecheck
  • make test-cov → 4967 passed
  • New regression tests:
    • test_probe_cli_auth_not_logged_in_exits_1 — exit 1 + valid JSON loggedIn:false
    • test_probe_cli_auth_subscription_with_env_api_key_reports_subscription
    • test_probe_cli_auth_api_key_only_uses_authmethod

Code Understanding and AI Usage

Did you use AI assistance?

  • Yes, I used AI assistance

If you used AI assistance:

  • I have reviewed every single line of the AI-generated code
  • I can explain the purpose and logic of each function/component I added
  • I have tested edge cases and understand how the code handles them
  • I have modified the AI output to follow this project's coding standards and conventions

Explain your implementation approach:

I found these two bugs by running claude auth status directly against the live CLI in each of the four documented auth states (subscription only, API key only, subscription + env key, no auth) and recording the exact JSON shape and exit code in each case. The third row (subscription + env key) showed the CLI emitting apiKeySource even when authMethod was claude.ai — that proves apiKeySource-presence is the wrong discriminator. The fourth row showed exit 1 with valid JSON, which the existing probe was treating as an opaque failure.

The fix has two parts. (1) Reorder _probe_cli_auth so JSON parsing happens before the exit-code check — the helper _try_parse_auth_status_stdout returns None only when stdout is genuinely not a JSON object, in which case the exit code becomes the next signal. (2) In the JSON-payload mapper, branch on authMethod first; only fall through to the legacy apiKeySource / email heuristic when authMethod is missing, which preserves backward compat with older CLI versions that don't emit it. I extracted the helpers so the JSON path is testable without spinning up a subprocess mock.

Edge cases I checked:

  • Older CLI without auth status (exit non-zero, non-JSON stderr) → still returns None (probe failure).
  • Older CLI with non-JSON stdout on exit 0 → existing negative-marker fallback path ("not logged in", "unauthenticated", etc.) still applies.
  • Empty stdout → _try_parse_auth_status_stdout returns None → exit code path takes over.
  • Malformed JSON / non-dict JSON (e.g. []) → _try_parse_auth_status_stdout returns None, same fallback.
  • loggedIn: true with no authMethod and no apiKeySource and no email → "Authenticated via Claude CLI." (legacy fallback).

Checklist before requesting a review

  • I have added proper PR title and linked to the issue
  • I have performed a self-review of my code
  • I can explain the purpose of every function, class, and logic block I added
  • I understand why my changes work and have tested them thoroughly
  • I have considered potential edge cases and how my code handles them
  • If it is a core feature, I have added thorough tests
  • My code follows the project's style guidelines and conventions

…thod (Tracer-Cloud#1260)

Follow-up to Tracer-Cloud#1295. Two cases of `claude auth status` were misclassified:

1. No auth + no env key → CLI exits 1 with valid JSON
   `{"loggedIn": false, "authMethod": "none"}`. The probe short-circuited
   on `returncode != 0` before parsing JSON and returned `None`, so the
   wizard surfaced "could not verify Claude Code CLI login" instead of the
   correct "Claude Code CLI requires login" — same logged_in=None surface
   Tracer-Cloud#1199 was about, just without the env-key fallback to rescue it.

2. Subscription + ANTHROPIC_API_KEY in env → CLI emits both
   `authMethod: "claude.ai"` and `apiKeySource: "ANTHROPIC_API_KEY"`. The
   prior code branched on `apiKeySource`-presence and reported
   "Authenticated via ANTHROPIC_API_KEY" even though the active method was
   the subscription. `authMethod` (`claude.ai` / `api_key` / `none`) is
   the authoritative discriminator; `apiKeySource` is set whenever the env
   contributes a key, regardless of which method is in use.

Implementation:
- Extract `_auth_status_from_json_payload` and `_try_parse_auth_status_stdout`
  so the JSON path is testable in isolation.
- `_probe_cli_auth` now parses stdout first; the exit-code error path is a
  fallback only when JSON is absent or unparseable.
- Detail-string branching uses `authMethod` first; falls back to
  `apiKeySource` / `email` for older CLI versions that omit `authMethod`.

Live verification on claude 2.1.123 (all four states):
- subscription only           → "Authenticated via Claude subscription (...)"
- api key only                → "Authenticated via ANTHROPIC_API_KEY."
- subscription + env API key  → "Authenticated via Claude subscription (...)"  (was: "via ANTHROPIC_API_KEY")
- no auth (fresh HOME)        → (False, "Not authenticated. ...")             (was: (None, "claude auth status failed: ..."))

Adds two regression tests:
- test_probe_cli_auth_not_logged_in_exits_1 — exit 1 + valid JSON loggedIn:false
- test_probe_cli_auth_subscription_with_env_api_key_reports_subscription
- test_probe_cli_auth_api_key_only_uses_authmethod (covers the api_key branch)

make lint / format-check / typecheck clean. make test-cov: 4967 passed.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

  • Fixes two bugs in _probe_cli_auth: (1) the function now parses proc.stdout as JSON before checking the exit code, so loggedIn: false with exit 1 is correctly reported as "not authenticated" instead of "could not verify"; (2) detail-string branching switches to authMethod as the authoritative discriminator, preventing apiKeySource from overriding the active method (e.g., subscription + env API key).
  • Both helper functions (_auth_status_from_json_payload, _try_parse_auth_status_stdout) are extracted to be independently testable, and the PR adds regression tests covering all four documented auth states plus a contract test pinning the stderr-only JSON behavior.
  • The previous comments about unrecognized authMethod values silently falling through to apiKeySource and the missing stderr-only JSON test have both been addressed in this revision.

Confidence Score: 5/5

Safe to merge — both bugs are correctly fixed, all edge cases are handled, and regression tests pin the new behavior.

No P0 or P1 issues found. The logic reordering is correct (JSON parse before exit-code check), the authMethod-first branching eliminates the mis-reporting, the legacy fallback is preserved for older CLI versions, and both previously flagged gaps (unrecognized authMethod fallthrough, missing stderr-only test) are now addressed.

No files require special attention.

Important Files Changed

Filename Overview
app/integrations/llm_cli/claude_code.py Correctly reorders probe logic (JSON parse before exit-code check) and introduces authMethod-first branching with a clean legacy fallback; all documented edge cases and CLI versions are handled.
tests/integrations/llm_cli/test_claude_code_adapter.py Adds five focused regression and contract tests covering the fixed bugs, unrecognized authMethod, and the stdout-only JSON contract; existing tests updated to reflect the new probe semantics.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([_probe_cli_auth]) --> B[subprocess.run 'claude auth status']
    B --> C{OSError / Timeout?}
    C -->|Yes| D[Return None, error message]
    C -->|No| E[_try_parse_auth_status_stdout stdout]
    E --> F{Valid JSON dict?}
    F -->|Yes| G[_auth_status_from_json_payload]
    F -->|No - empty / invalid / non-dict| H{returncode != 0?}
    H -->|Yes| I[Return None, 'claude auth status failed']
    H -->|No| J{Negative markers in plain text?}
    J -->|Yes| K[Return False, 'Not authenticated']
    J -->|No| L[Return True, 'Authenticated via Claude CLI']
    G --> M{loggedIn falsy?}
    M -->|Yes| N[Return False, 'Not authenticated']
    M -->|No| O{authMethod == api_key?}
    O -->|Yes| P[Return True, via apiKeySource]
    O -->|No| Q{authMethod == claude.ai?}
    Q -->|Yes| R[Return True, via subscription + email]
    Q -->|No| S{authMethod non-empty?}
    S -->|Yes| T[Return True, via authMethod verbatim + email]
    S -->|No - legacy fallback| U{apiKeySource present?}
    U -->|Yes| V[Return True, via apiKeySource]
    U -->|No| W{email present?}
    W -->|Yes| X[Return True, via subscription + email]
    W -->|No| Y[Return True, Authenticated via Claude CLI]
Loading

Reviews (2): Last reviewed commit: "fix(claude-code): guard unrecognized aut..." | Re-trigger Greptile

Comment thread app/integrations/llm_cli/claude_code.py
Comment thread tests/integrations/llm_cli/test_claude_code_adapter.py
@muddlebee
Copy link
Copy Markdown
Collaborator

good work. review fixes and good to merge 👍

Future authMethod values (e.g. oauth/sso) emitted alongside a populated
apiKeySource were skipping both explicit branches and landing on the
legacy heuristic, mis-reporting the env var as the active source — the
same kind of mis-attribution this PR fixes for claude.ai. Surface the
authMethod verbatim instead. Adds the missing stderr-only-JSON contract
test so a future stderr-parsing refactor cannot silently change
_probe_cli_auth's return value.
@muddlebee
Copy link
Copy Markdown
Collaborator

@greptileai review

@muddlebee muddlebee merged commit 16f62f7 into Tracer-Cloud:main May 5, 2026
9 of 10 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🧠 @Davidson3556 opened a PR. Maintainers feared them. CI genuflected. It merged. 🚨


👋 Join us on Discord - OpenSRE : hang out, contribute, or hunt for features and issues. Everyone's welcome.

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.

Audit Claude Code CLI: subscription login vs API key and cross-OS parity

2 participants