From 89463541004e2c58d0d37522154ddbd29b91603d Mon Sep 17 00:00:00 2001 From: archdex-art Date: Fri, 17 Jul 2026 12:46:22 +0530 Subject: [PATCH] fix: don't offer Claude subscription mode without real credential evidence 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--/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. --- app/DEPLOY.md | 2 + app/src/app/settings/page.tsx | 8 +++ app/src/lib/agents/assistant.ts | 34 +++++++++++-- app/src/lib/settings.ts | 34 +++++++++++++ app/tests/assistant.test.ts | 31 +++++++++++- app/tests/subscriptionCredentials.test.ts | 60 +++++++++++++++++++++++ 6 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 app/tests/subscriptionCredentials.test.ts diff --git a/app/DEPLOY.md b/app/DEPLOY.md index 38405e3..c286412 100644 --- a/app/DEPLOY.md +++ b/app/DEPLOY.md @@ -47,6 +47,7 @@ docker compose up --build # http://localhost:4000 | `CG_SESSION_SECRET` | app runtime | unset (= GitHub sign-in disabled) | Encrypts the GitHub session cookie (AES-256-GCM). Required alongside the two vars above for GitHub sign-in to activate. Any long random string; rotating it signs everyone out. | | `CG_OWNER_GITHUB_LOGIN` | app runtime | unset (= no owner-lock) | Restrict the **entire app** — every page and API route, including the normally-anonymous public bucket — to one or more GitHub logins (comma-separated, case-insensitive). Requires GitHub sign-in to be fully configured too (fails closed with a 500 otherwise). See below. | | `ANTHROPIC_API_KEY` | app runtime | unset (= Claude AI Assistant hidden) | Enables the Editor tab's AI Assistant using Claude (Claude Agent SDK) — see below. Core product features never need this. | +| `CLAUDE_CODE_OAUTH_TOKEN` | app runtime | unset | Alternative to `ANTHROPIC_API_KEY`: authenticates Claude via a Pro/Max/Team subscription login instead of pay-per-token API billing. The only way subscription mode can work on a hosted/container deployment (no persistent home directory, no interactive terminal for `claude login`) — see below. | | `CG_LOCAL_LLM_BASE_URL` | app runtime | unset (= local-model AI Assistant hidden) | Base URL of an OpenAI-compatible chat-completions endpoint (Ollama, LM Studio, llama.cpp `server`, vLLM, …), e.g. `http://localhost:11434/v1`. Required alongside `CG_LOCAL_LLM_MODEL` — see below. | | `CG_LOCAL_LLM_MODEL` | app runtime | unset | Model name/tag to request from that endpoint, e.g. `qwen2.5-coder:7b`. Required alongside `CG_LOCAL_LLM_BASE_URL`. | | `CG_LOCAL_LLM_API_KEY` | app runtime | unset (sends `local`) | Optional bearer token if your local server's endpoint requires one; most (Ollama, LM Studio) don't. | @@ -93,6 +94,7 @@ Runs in-process against the repo's live workspace directory. 1. Set `ANTHROPIC_API_KEY=` on the deployment (and/or in a local `.env.local` for dev). Restart/redeploy. 2. `npm install` already pulls the correct platform binary via `@anthropic-ai/claude-agent-sdk`'s `optionalDependencies` (Linux glibc/musl x64+arm64, macOS, Windows), so this works in the Docker image with no extra install step. +3. **Subscription mode (`CLAUDE_CODE_OAUTH_TOKEN` / "Use my Claude subscription" toggle) requires real evidence of a working login on this exact server process** — either `CLAUDE_CODE_OAUTH_TOKEN` set as an env var, or a `~/.claude/.credentials.json` file from having run `claude login` on that same host. On Render (and most container hosts), the home directory is NOT on the persistent disk and there's no interactive terminal to run `claude login` in the first place, so a credentials file can never exist there — `CLAUDE_CODE_OAUTH_TOKEN` is the only way subscription mode can work on this deployment model. CodeGraph checks for this evidence server-side (`claudeSubscriptionCredentialsAvailable()` in `lib/settings.ts`) before ever offering Claude via the subscription path — toggling the Settings checkbox alone, with no real credentials, correctly leaves the Claude tab unavailable instead of producing a broken chat session ("Not logged in — Please run /login", followed by "/login isn't available in this environment", since the SDK session is headless and can't prompt interactively). ### Local model (any OpenAI-compatible server) Talks over plain HTTP to a model server running on your own hardware — no data leaves the machine running CodeGraph. Tested against the OpenAI-compatible `/v1/chat/completions` shape that Ollama, LM Studio, llama.cpp's `server`, vLLM, and text-generation-webui all implement; tool-calling quality (and therefore how well the assistant can actually edit files) depends entirely on the model you pick — recent tool-calling-tuned models (e.g. Qwen2.5-Coder, Llama 3.1+) work noticeably better than older/small ones. diff --git a/app/src/app/settings/page.tsx b/app/src/app/settings/page.tsx index 91aab5a..bfe4be1 100644 --- a/app/src/app/settings/page.tsx +++ b/app/src/app/settings/page.tsx @@ -320,6 +320,14 @@ export default function SettingsPage() { Uses your subscription's included usage instead of per-token API billing. An API Key above, if set, always takes priority over this.

+ {useSubscription && settings && !settings.claudeSubscriptionUsable && ( +

+ ⚠ This server has no usable Claude Code login right now (no CLAUDE_CODE_OAUTH_TOKEN{" "} + and no local claude login session) — starting a chat with this toggle on and no + API Key set above will fail. Set an Anthropic API Key above instead, or ask the site operator to configure{" "} + CLAUDE_CODE_OAUTH_TOKEN. +

+ )}