Skip to content

fix: don't offer Claude subscription mode without real credential evidence#17

Merged
archdex-art merged 1 commit into
mainfrom
fix/subscription-mode-credential-check
Jul 17, 2026
Merged

fix: don't offer Claude subscription mode without real credential evidence#17
archdex-art merged 1 commit into
mainfrom
fix/subscription-mode-credential-check

Conversation

@archdex-art

Copy link
Copy Markdown
Owner

Bug report (with screenshot)

Typing "hi" to the AI Assistant returned Not logged in · Please run /login. 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" (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 either CLAUDE_CODE_OAUTH_TOKEN or a ~/.claude/.credentials.json file (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 run claude 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() in lib/settings.ts — true only with real evidence (CLAUDE_CODE_OAUTH_TOKEN set 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.

  • Settings page: inline warning when the toggle is on but unusable.
  • Defense-in-depth: sendMessage detects 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

  • Updated aiAssistantConfigured tests to assert the corrected behavior (the old test literally asserted the bug).
  • 4 new tests proving the toggle and credential-evidence are independent facts.
  • Live, unmocked smoke test: ran a dev server with CG_CLAUDE_USE_SUBSCRIPTION=true and no credentials (this environment's real state) — confirmed the settings view reports claudeSubscriptionUsable: false, and a real indexed repo's assistant route 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.

…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.
@archdex-art
archdex-art merged commit d482a8b into main Jul 17, 2026
2 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