Skip to content

fix: resolve Claude auth status via claude auth status instead of a credentials file - #1035

Open
zscgeek wants to merge 1 commit into
siteboon:mainfrom
zscgeek:fix/claude-auth-keychain-status
Open

fix: resolve Claude auth status via claude auth status instead of a credentials file#1035
zscgeek wants to merge 1 commit into
siteboon:mainfrom
zscgeek:fix/claude-auth-keychain-status

Conversation

@zscgeek

@zscgeek zscgeek commented Jul 18, 2026

Copy link
Copy Markdown

Summary

The Settings → Agents → Claude → Account tab always reports "Disconnected" / "Claude CLI is not authenticated" for OAuth-based logins (claude /login), even when claude itself is genuinely logged in and working fine in a real shell.

Root cause

claude-auth.provider.ts's checkCredentials() only ever checks a plaintext ~/.claude/.credentials.json file for OAuth login state. On macOS, Claude Code actually stores OAuth credentials in the system Keychain, under a service name that includes a hash derived from CLAUDE_CONFIG_DIR when it's set (verified: a distinct Keychain entry — Claude Code-credentials for the default profile, Claude Code-credentials-<hash> for a CLAUDE_CONFIG_DIR-scoped profile). So the file the provider checks for simply never exists for any OAuth login on this platform, and it always falls through to "not authenticated" — regardless of whether the CLI is actually logged in, and regardless of which CLAUDE_CONFIG_DIR/profile is active.

Fix

Reimplementing that Keychain service-name hashing isn't a stable contract to build against (it's an internal detail of the CLI, not a documented interface). Instead, this shells out to claude auth status --json, inheriting the current process environment (so CLAUDE_CONFIG_DIR, if set, is naturally respected) and lets the CLI itself resolve however it actually stores credentials — file, Keychain, or anything else it might use in the future. The plaintext-file check is kept as a fast path first, for platforms/older versions that do use a file.

$ claude auth status
{
  "loggedIn": true,
  "authMethod": "claude.ai",
  "apiProvider": "firstParty",
  "email": "rj@example.com",
  "orgId": "...",
  "orgName": "...",
  "subscriptionType": "team"
}

Testing

  • npm run typecheck — clean
  • npm run lint — no new errors
  • npm run build — clean
  • Invoked the compiled ClaudeProviderAuth directly (isolated from any other change, based straight off current main) for three cases:
    • default-profile OAuth login → correctly reports the account email, authenticated: true
    • CLAUDE_CONFIG_DIR-scoped OAuth login to a different account → correctly reports that different account's email
    • an unauthenticated CLAUDE_CONFIG_DIR → correctly reports authenticated: false with the expected error message

Note

This is independent of #1034 (which fixes CLAUDE_CONFIG_DIR handling for projects/sessions/settings elsewhere in the app) — either can merge first, they don't depend on each other.

Summary by CodeRabbit

  • Bug Fixes
    • Improved Claude authentication detection by checking local credentials first and falling back to the Claude Code CLI when needed.
    • Added clearer handling for expired, missing, unreadable, or invalid credentials.
    • Improved recognition of authentication status, login email, authentication method, and active CLI profiles.
    • Added more specific error messages when authentication checks fail.

… file

claude-auth.provider.ts only checked a plaintext ~/.claude/.credentials.json
for OAuth login state. On macOS, Claude Code actually stores OAuth
credentials in the system Keychain, under a service name that includes a
hash derived from CLAUDE_CONFIG_DIR when it's set (a different Keychain
entry per profile) - so the file check found nothing and the Account tab
always reported "not authenticated" for any OAuth-based login, on any
profile, on any host where Keychain (or an equivalent OS credential store)
is actually used.

Reimplementing that Keychain hashing scheme isn't a stable contract to build
against, so this shells out to `claude auth status --json` instead
(inheriting the process env, including CLAUDE_CONFIG_DIR when set) and lets
the CLI itself resolve however it actually stores credentials on the current
platform/profile. The plaintext-file check is kept as a fast-path fallback
for platforms/versions that do use a file.

Verified directly against the compiled provider for three cases: a
default-profile OAuth login, a CLAUDE_CONFIG_DIR-scoped OAuth login to a
different account, and an unauthenticated CLAUDE_CONFIG_DIR - all three now
report the correct account (or correctly report unauthenticated).
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Claude credential detection now checks local OAuth credentials first, then falls back to claude auth status --json. The implementation handles token expiry, missing credentials, CLI failures, JSON parsing, login state, organization email formatting, and authentication method mapping.

Changes

Claude authentication resolution

Layer / File(s) Summary
Local credential interpretation
server/modules/providers/list/claude/claude-auth.provider.ts
readCredentialsFile() handles missing, invalid, expired, and valid plaintext OAuth credentials with distinct results.
CLI authentication fallback
server/modules/providers/list/claude/claude-auth.provider.ts
checkCredentials invokes CLI status when local credentials are unavailable, while checkCliAuthStatus() parses login state, account details, organization information, and authentication method.

Sequence Diagram(s)

sequenceDiagram
  participant checkCredentials
  participant readCredentialsFile
  participant checkCliAuthStatus
  checkCredentials->>readCredentialsFile: read local OAuth credentials
  readCredentialsFile-->>checkCredentials: return credential result or null
  checkCredentials->>checkCliAuthStatus: run claude auth status --json when null
  checkCliAuthStatus-->>checkCredentials: return mapped auth status
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: blackmammoth, viper151

Poem

A rabbit checks the token’s trail,
Then asks the CLI without fail.
OAuth sleeps or keys reply,
Login states hop neatly by.
Claude’s auth path now knows why.

🚥 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 matches the main change: switching Claude auth detection from a credentials file to claude auth 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
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/modules/providers/list/claude/claude-auth.provider.ts`:
- Around line 173-192: Replace the synchronous spawn.sync call in the Claude
authentication status flow with an asynchronous execFile-based implementation,
using the existing async function context and preserving the 10-second timeout,
JSON output, environment, and argument handling. Ensure spawn failures,
timeouts, and non-zero exits are caught and mapped to the existing
authentication error response rather than falling through to
missingCredentialsError; update result.stdout handling to match the asynchronous
API.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 30761b17-9b7f-4f42-9be8-e57bce10f637

📥 Commits

Reviewing files that changed from the base of the PR and between 27eaf01 and 1cd8bc2.

📒 Files selected for processing (1)
  • server/modules/providers/list/claude/claude-auth.provider.ts

Comment on lines +173 to +192
let result;
try {
result = spawn.sync(cliPath, ['auth', 'status', '--json'], {
timeout: 10000,
encoding: 'utf8',
env: process.env,
});
} catch {
return {
authenticated: false,
email: null,
method: null,
error: 'Unable to check Claude authentication status. Run claude /login again.',
};
}

const stdout = typeof result.stdout === 'string' ? result.stdout.trim() : '';
if (!stdout) {
return { authenticated: false, email: null, method: null, error: missingCredentialsError };
}

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.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Avoid blocking the event loop and handle execution errors correctly.

Using synchronous spawn.sync inside an async function blocks the Node.js main thread for up to 10 seconds (the configured timeout). In a server environment, this stalls all concurrent requests and degrades availability.

Additionally, spawn.sync does not throw an exception when it fails to start the process (e.g., missing executable) or when it times out; it returns an error in result.error. The current try/catch block will not catch these failures, causing the code to swallow the error and fall through to reporting a misleading "missing credentials" error due to an empty stdout.

Consider replacing spawn.sync with an asynchronous alternative (such as child_process.execFile wrapped in util.promisify), which correctly throws on spawn failures, timeouts, and non-zero exit codes without blocking the event loop.

⚡ Proposed fix using promisified `execFile`

Assuming you have access to a promisified execFile (e.g., const execFileAsync = util.promisify(require('child_process').execFile)):

-    let result;
+    let stdout = '';
     try {
-      result = spawn.sync(cliPath, ['auth', 'status', '--json'], {
+      const result = await execFileAsync(cliPath, ['auth', 'status', '--json'], {
         timeout: 10000,
         encoding: 'utf8',
         env: process.env,
       });
+      stdout = result.stdout.trim();
     } catch {
       return {
         authenticated: false,
         email: null,
         method: null,
         error: 'Unable to check Claude authentication status. Run claude /login again.',
       };
     }
 
-    const stdout = typeof result.stdout === 'string' ? result.stdout.trim() : '';
     if (!stdout) {
       return { authenticated: false, email: null, method: null, error: missingCredentialsError };
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/modules/providers/list/claude/claude-auth.provider.ts` around lines
173 - 192, Replace the synchronous spawn.sync call in the Claude authentication
status flow with an asynchronous execFile-based implementation, using the
existing async function context and preserving the 10-second timeout, JSON
output, environment, and argument handling. Ensure spawn failures, timeouts, and
non-zero exits are caught and mapped to the existing authentication error
response rather than falling through to missingCredentialsError; update
result.stdout handling to match the asynchronous API.

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