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.
+ ⚠ 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.
+