Skip to content

fix(claude): API-key Claude users are told they're not signed in - #25163

Merged
brennanb2025 merged 5 commits into
mainfrom
brennanb2025/claude-api-key-signed-in
Oct 6, 2026
Merged

brennanb2025 merged 5 commits into
mainfrom
brennanb2025/claude-api-key-signed-in

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​32 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​32
Prod 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​5 $\color{#cf222e}{\Huge{\mathbf{−}}}$​2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3

ELI5

If you use Claude with an API key instead of a Claude subscription login, Orca's native chat told you "Claude is not signed in for the selected account" and would not open, even though claude worked fine for you in a terminal. Now the chat opens for API-key users. Someone with no credentials at all still gets the same sign-in message as before.

What Changed

The problem. When a native (structured) Claude chat starts, Orca reads the account Claude reports in its first handshake answer (the "initialize" response). Orca treated tokenSource: "none" as "not signed in". But Claude reports tokenSource: "none" for API-key users too, and names the key in a separate field, apiKeySource. Orca never read that field, so it shut down every chat that ran on an ANTHROPIC_API_KEY.

What a real Claude CLI (2.1.280) reports at initialize, captured in an isolated home with dummy credentials and no message sent:

How Claude is authenticated account at initialize Before After
Nothing (no login, no key) tokenSource: "none" refused refused (unchanged)
ANTHROPIC_API_KEY in the environment tokenSource: "none", apiKeySource: "ANTHROPIC_API_KEY" refused opens
ANTHROPIC_API_KEY in settings.json env tokenSource: "none", apiKeySource: "ANTHROPIC_API_KEY" refused opens
Console account via /login (from Claude's own source, not captured) tokenSource: "none", apiKeySource: "/login managed key" refused opens
ANTHROPIC_AUTH_TOKEN (env, settings env, or with ANTHROPIC_BASE_URL) tokenSource: "ANTHROPIC_AUTH_TOKEN" opens opens
CLAUDE_CODE_OAUTH_TOKEN tokenSource: "CLAUDE_CODE_OAUTH_TOKEN" opens opens
apiKeyHelper in settings tokenSource: "apiKeyHelper", apiKeySource: "apiKeyHelper" opens opens
Bedrock / Vertex apiProvider: "bedrock" / "vertex", no tokenSource opens opens

So the API-key rows were the only ones affected.

The user-facing change. A Claude API-key user who opens a native chat now gets a working chat instead of the "not signed in" failure. Nothing else changes: a user with no credentials still gets the same sign-in guidance at the same point.

The mechanism. claudeInitializationAuthError (src/main/claude/claude-structured-init-proof.ts) now refuses only when Claude reports no token source and no API-key source (apiKeySource absent or "none"). It still reads only the initialize answer Orca already receives. There is no new probe, setting or stored state.

Why

  • Narrowest correct rule. Claude itself reports every credential it found in those two fields. Checking both covers all the cases in the table. Reading environment variables or settings files in Orca would duplicate Claude's own credential resolution and could drift from it (a key in a project settings file, a helper, a managed key), so this change doesn't do that.
  • Considered: dropping the up-front check entirely and relying on the first turn's authentication_failed reply, which is how the common pattern detects a signed-out Claude. Today that would be a regression: Orca doesn't read that reply yet, so a signed-out user's first message would show Claude's raw "Not logged in · Please run /login" twice (once as a reply, once as an error row) instead of Orca's sign-in message. Difference from the common pattern (temporary): Orca still refuses at open when Claude reports no credentials at all. That keeps today's message for those users, and it can no longer misfire on a configured key. Follow-up: handle a turn's authentication_failed as Orca's typed "not signed in" failure, then remove the open-time check. That follow-up changes what a signed-out user sees (they'd learn at their first message, not at open), so it needs its own decision.

Linked Issue

Found while building #25152, whose isolated test of the Claude SDK with ANTHROPIC_API_KEY set reported tokenSource: "none" with apiKeySource: "ANTHROPIC_API_KEY".

Visual Proof

Before (b354aab18e1, main at this PR's merge base) After (3401ae76fbc, this PR)
A. API-key user opens a native Claude chat and sends "hello" before: the chat shows "Claude is not signed in for the selected account" and the message is not sent after: the chat answers "All done: the stand-in finished its commands."
B. No credentials (control) not captured (the before build refuses every tokenSource: "none" account, as in A) after, no credentials: the same "not signed in" message as before

Hidden, isolated e2e builds on the M4Air test Mac, driven over CDP by a Grok QA agent. claude is a stand-in that answers initialize with account: {tokenSource: "none", apiKeySource: "ANTHROPIC_API_KEY"} for A and {tokenSource: "none"} for B; no real Claude or account was used. The "Enjoying Orca?" card in the A-after shot is the existing GitHub-star prompt that follows a first successful chat turn; this PR does not add it.

Testing

  • src/main/claude/claude-structured-session-startup.test.ts: new cases using the account shapes in the table above. The API-key and Console /login cases fail on main and pass here; the others (auth token, helper, Bedrock) pin the cases that already worked; "apiKeySource: "none"" still refuses.

  • Existing "not signed in" tests still pass: adapter, runtime integration (claude-structured-session-integration.test.ts), and command lifecycle: 104 tests on the final head, after merging current main.

  • Node typecheck, oxlint, format check and the changed-code quality gate all pass.

  • Real Claude CLI suite (ORCA_REAL_CLAUDE_CLI_TEST=1, subscription login): 10/10 passed on the final head.

  • The capture in the table: a script that drives the Claude SDK directly under env -i, with a temporary HOME and CLAUDE_CONFIG_DIR and dummy credentials only. It sends no message, so nothing reaches the network with a key.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Cross-platform: a pure check on the handshake answer, no platform code. SSH/remote: runs wherever Claude runs, on the same answer. Mobile and paired clients: no wire change; they just stop receiving a false "not signed in" failure. Performance: none.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

brennanb2025 and others added 3 commits October 4, 2026 00:05
…t signed in"

Claude reports tokenSource "none" beside apiKeySource "ANTHROPIC_API_KEY" when it runs on
an API key (environment or settings env). The startup check read only tokenSource, so it
refused those chats as signed out. Refuse only when Claude reports no token source and no
API-key source.

Co-Authored-By: Claude <noreply@anthropic.com>
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review and QA summary

The problem. People who run Claude with an API key instead of a Claude subscription login couldn't use Orca's native Claude chat. When a chat starts, Claude reports which credentials it found. For an API key it says "no login token" in one field and names the key in another field. Orca read only the first field, so it closed the chat with "Claude is not signed in for the selected account", even though claude worked for these users in a terminal.

What changes for users. API-key users can now open a native Claude chat and use it. That covers a key in the environment, a key in Claude's settings.json, and a key from signing in to a Console account with /login. Someone with no credentials at all sees exactly what they saw before. Auth-token, key-helper, Bedrock and Vertex users were never affected and stay working.

What the review checked and fixed

  • An independent review and an architecture review found no blocking problems. The fix is the only place on the chat-open path that decides "not signed in". It now refuses only when Claude reports neither a login token nor an API key.
  • Fixed during review: a test for Console /login keys, which this change also fixes. The PR description now labels the remaining check at chat open as temporary and names the follow-up.
  • Readiness checklist on the final diff: pass. No stored data, wire format or platform-specific code changes.

Deferred (pre-existing, not caused by this PR)

  • P2: Orca doesn't recognise Claude's sign-in failure on a message. An expired or wrong key, or a gateway that rejects the request, shows Claude's raw "Not logged in · Please run /login" twice: once as a reply and once as an error line. The planned follow-up turns that into Orca's own "not signed in" message on the failed message. After that, the check at chat open can be removed, which is how the common pattern works. That last step changes when a signed-out user finds out (at their first message instead of at open), so it needs its own decision.
  • P3: the usage list in the status bar labels API-key users "not signed in".
  • P3: a refused chat shows the "not signed in" sentence twice, as a banner and again under the unsent message. The base build does the same.

What was verified

  • A real Claude CLI (2.1.280) was run in an isolated home with dummy credentials and no message sent. For each kind of credential, I recorded what it reports when a chat starts (table in the description). Only the API-key cases were being refused.
  • New tests fail on main and pass here. The existing "not signed in" tests and related tests pass: 104 on the final head after merging current main. The real-Claude test suite passed 10/10 on the final head. The Node typecheck is clean apart from the known stream-json errors. CI is green.
  • QA on a remote Mac in an isolated test app, with a stand-in claude that reports an API-key account, driven by a non-Claude agent. Before: the chat refused with "not signed in". After: the chat replied. A no-credentials control on the new build still shows "not signed in". Screenshots are in the description.

What wasn't verified

  • A real Claude run with a real API key sending a message. No real or valid key was used.
  • Windows, Linux and SSH were not exercised live. The change is a pure check on Claude's start-up answer, with no platform code.
  • The Console /login shape comes from Claude's own source, not from a captured run.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 5, 2026 20:39
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8885dd05-714f-445b-9a38-24421129d1a7
📥 Commits

Reviewing files that changed from the base of the PR and between 9ccdc74 and eb9fe48.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b2e0f45a-5cf4-4b25-8a5e-2b6c9cba7540
📥 Commits

Reviewing files that changed from the base of the PR and between b354aab and 3401ae7.

📒 Files selected for processing (2)
  • src/main/claude/claude-structured-init-proof.ts
  • src/main/claude/claude-structured-session-startup.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

claudeInitializationAuthError now checks apiKeySource before returning a not-signed-in refusal. Startup tests cover five authentication configurations that should not end the session, plus a first-party account with apiKeySource: 'none' that should end with sign-in guidance.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 3401a

Startup now accepts API-key sources reported by Claude while retaining the no-credentials refusal. Real API-key CLI behavior was not verified, but no concrete merge-blocking risk is established.

Security Architecture Review

Security architecture risk: ⚪ Minimal · up to 3401a

The change recognizes API-key authentication without changing credential handling, tool permissions or session identity checks. No material security risk was found in the changed startup behavior.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed exposure is admission of local structured sessions whose CLI reports an API-key source while reporting no token source. These sessions use the existing launch context and permission callbacks rather than a new authority path.

Trust Boundaries and Controls

  • inferred — The application continues to trust authentication-source metadata from its selected Claude CLI through the SDK control interface, not from chat-message content. Accepting an API-key source changes the local sign-in gate; it does not itself validate a key or confer provider-side authorization.
  • observed — The existing connection construction supplies an explicit child environment and the configured tool-permission callback. Startup separately verifies provider-session identity after authentication-source admission.

Resilience and Maintainability Implications

  • observed — Connection shutdown remains shared and repeatable: concurrent closes share a promise, an unproven close permits another attempt, and process exit or close releases the credential-refresh gate. API-key admission does not alter these recovery controls.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: Claude API-key users can open chats without being incorrectly told they are not signed in.
Description check ✅ Passed The description explains the problem, user impact, implementation, alternatives, linked issue context, visual proof, and testing. It also addresses the template’s cross-platform and compatibility note…
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
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • claudeInitializationAuthError no longer refuses when Claude reports tokenSource: "none" but names a credential in account.apiKeySource; it refuses only when apiKeySource is absent or "none". Verified against the bundled SDK types (SDKControlInitializeResponse.account: AccountInfo, with optional tokenSource / apiKeySource / apiProvider), so the field is read from the right place for the initialize response.
  • New it.each cases in claude-structured-session-startup.test.ts pin the API-key, Console /login, apiKeyHelper, ANTHROPIC_AUTH_TOKEN and third-party-provider accounts as starting, plus a negative case (apiKeySource: "none") that still refuses. The API-key rows genuinely fail on main, so the coverage is real.

Confirmed locally: claude-structured-session-startup.test.ts (16 passed) and claude-structured-session-adapter.test.ts (39 passed); oxlint and oxfmt clean. The refusal remains strictly narrower than before, and both apiKeySource branches (missing via the pre-existing test, "none" via the new one) stay covered.

One behavior worth being aware of, already framed as a follow-up in the PR body: a user with a configured but invalid key now opens the chat and fails at the first turn with Claude's raw auth error, since Orca does not yet map a turn's authentication_failed to its typed sign-in failure.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@brennanb2025
brennanb2025 merged commit db078b6 into main Oct 6, 2026
30 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.

1 participant