fix: don't offer Claude subscription mode without real credential evidence#17
Merged
Merged
Conversation
…dence Bug report (with screenshot): typing "hi" to the AI Assistant returned "Not logged in - Please run /login", then running "/login" returned "/login isn't available in this environment." ## Root cause (evidence, not assumption) Extracted the CLI's full error-message catalog from the actual compiled `claude` binary (`strings` on `@anthropic-ai/claude-agent-sdk-<platform>-<arch>/claude`). Confirmed "Not logged in / Please run /login" is a DISTINCT, mutually exclusive error class from "Invalid API key / Fix external API key" (the previous bug) -- it fires specifically when no API key is even attempted and the CLI falls back to checking for an OAuth/subscription login, finding none. `aiAssistantConfigured()` treated the "Use my Claude subscription" toggle alone as sufficient evidence Claude was usable: !!effectiveAnthropicApiKey(userId) || effectiveUseClaudeSubscription(userId) But a subscription login requires either `CLAUDE_CODE_OAUTH_TOKEN` or a `~/.claude/.credentials.json` file (confirmed via the same binary strings -- exact path extracted, not guessed). On this app's Docker deployment (Render), the home directory is NOT on the persistent `CG_DATA_DIR` volume, and there is no interactive TTY to ever run `claude login` in the first place -- so a credentials file can never exist there, and the toggle being checked with no ANTHROPIC_API_KEY meant every session silently entered a subscription-auth path this server can never satisfy. The in-process SDK session is also headless (no real TTY), so the CLI's own "/login isn't available in this environment" is expected -- confirmed by the same strings extraction (`isn't available in this environment.` is a generic message for any interactive-only command run headless). ## Fix New `claudeSubscriptionCredentialsAvailable()` in `lib/settings.ts`: returns true only when `CLAUDE_CODE_OAUTH_TOKEN` is set or the real credentials file exists on this exact server. `aiAssistantConfigured()` and session creation now require this real evidence, not just the user's toggle preference -- so toggling the box with no usable credentials correctly leaves the Claude tab unavailable (falls back to "no provider configured" with a link to Settings) instead of producing a broken chat session. Settings page: added an inline warning when the toggle is on but `claudeSubscriptionUsable` is false, so the user gets proactive feedback instead of discovering it mid-chat. Defense-in-depth: `assistant.ts`'s `sendMessage` now also detects the CLI's known auth-failure onboarding text at message time (covers a token being revoked, or a credentials file removed, after the process already started) and converts it to a clear app-level error instead of letting it render as if Claude itself said "Not logged in" to the user's message. ## Verification - Updated `aiAssistantConfigured` tests to assert the corrected behavior (toggle alone -> false; toggle + CLAUDE_CODE_OAUTH_TOKEN -> true) -- the old test asserted the exact bug. - 4 new tests (`subscriptionCredentials.test.ts`) proving the toggle and the credential-evidence check are independent facts. - Live, unmocked smoke test against a running dev server with `CG_CLAUDE_USE_SUBSCRIPTION=true` set and no credentials (this environment's real state): confirmed `/api/settings/assistant` reports `useClaudeSubscription: true, claudeSubscriptionUsable: false`, and a real indexed repo's `/api/repos/:id/assistant` now reports `providers.claude: false` instead of silently offering a broken session. - Full suite 230/230, tsc --noEmit clean, lint baseline unchanged, next build clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug report (with screenshot)
Typing "hi" to the AI Assistant returned
Not logged in · Please run /login. Running/loginreturned/login isn't available in this environment.Root cause (evidence, not assumption)
Extracted the CLI's full error-message catalog from the actual compiled
claudebinary (stringson@anthropic-ai/claude-agent-sdk-<platform>-<arch>/claude). Confirmed "Not logged in / Please run /login" is a distinct, mutually exclusive error class from "Invalid API key" (the previous bug, PR #16) — it fires specifically when no API key is even attempted and the CLI falls back to checking for an OAuth/subscription login, finding none.aiAssistantConfigured()treated the "Use my Claude subscription" toggle alone as sufficient evidence Claude was usable. But a subscription login requires eitherCLAUDE_CODE_OAUTH_TOKENor a~/.claude/.credentials.jsonfile (exact path extracted from the binary, not guessed). On this app's Docker deployment, the home directory is NOT on the persistent volume and there's no interactive TTY to ever runclaude login— so a credentials file can never exist there, and the toggle alone silently routed every session into an auth path this server can never satisfy.Fix
New
claudeSubscriptionCredentialsAvailable()inlib/settings.ts— true only with real evidence (CLAUDE_CODE_OAUTH_TOKENset or the credentials file present).aiAssistantConfigured()now requires this, not just the toggle — so an unusable subscription config correctly leaves Claude unavailable ("no provider configured") instead of producing a broken chat session.sendMessagedetects the CLI's known auth-failure text at message time (covers a token revoked mid-runtime) and converts it to a clear app-level error instead of rendering as fake chat content.Verification
aiAssistantConfiguredtests to assert the corrected behavior (the old test literally asserted the bug).CG_CLAUDE_USE_SUBSCRIPTION=trueand no credentials (this environment's real state) — confirmed the settings view reportsclaudeSubscriptionUsable: false, and a real indexed repo's assistant route now reportsproviders.claude: falseinstead of silently offering a broken session.tsc --noEmitclean, lint baseline unchanged,next buildclean.