fix(anthropic): stop injecting env.CLAUDE_CODE_OAUTH_TOKEN — let CLI auto-refresh

OLP was injecting accessToken from ~/.claude/.credentials.json into the
spawn env as CLAUDE_CODE_OAUTH_TOKEN unconditionally. This defeated
claude CLI's built-in OAuth refresh: when the env var is present, the
CLI reads it directly and never touches credentials.json, so expired
accessTokens were never swapped using the refreshToken sitting right
there in the file. Result: hard 401 cascade every ~8h (access token
TTL) requiring manual `security find-generic-password → scp → PI231`
cycle.

Empirical 2026-05-28 spike on PI231 v2.1.104:
- expiresAt = 1 min ago (simulated expiry)
- spawn claude -p WITHOUT CLAUDE_CODE_OAUTH_TOKEN env
- response succeeded, AND credentials.json.expiresAt advanced to
  ~8h in the future
- token refresh handled internally by claude CLI

Fix: remove the env injection in _spawnAndStream. buildSpawnEnv still
forwards process.env (minus ANTHROPIC_*), so if the operator explicitly
sets CLAUDE_CODE_OAUTH_TOKEN at OLP boot, it's still honored — for
users on ephemeral CI / no-file-creds setups. The readAuthArtifact()
call remains as a preflight check (verify creds exist before spawn)
but the read value is no longer forwarded.

Tests: 793 → 795
- 41h: regression guard — no CLAUDE_CODE_OAUTH_TOKEN in env when only
  file/keychain auth is available
- 41h-2: operator-set process.env passthrough still works

User-facing impact: bot-down-every-8-hours issue resolved. Hermes +
OpenClaw clients no longer require periodic keychain → PI231 copy.

Authority:
- empirical PI231 v2.1.104 spike 2026-05-28 (no public CLI doc cites
  refresh behavior; verified by simulating expiry + observing file
  update post-spawn)
- ADR 0009 Amendment 1 § "Caveats" — implementation must remain robust
  to OAuth flow changes (this fix removes a hard-coded assumption that
  was actively harmful)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 16:52:43 +10:00
co-authored by Claude Opus 4.7
parent dd0c821272
commit dbac5f5521
2 changed files with 98 additions and 3 deletions
+31 -3
View File
@@ -864,9 +864,37 @@ async function* _spawnAndStream(irRequest, authContext, spawnImpl) {
const bin = resolveClaudeBin();
const env = buildSpawnEnv();
// Inject OAuth token so the CLI can authenticate.
// We set it explicitly here so the plugin is self-contained.
env.CLAUDE_CODE_OAUTH_TOKEN = auth.accessToken;
// OAuth credential strategy:
//
// Previously this plugin set env.CLAUDE_CODE_OAUTH_TOKEN to the accessToken
// value read from ~/.claude/.credentials.json (or keychain). That worked at
// call time but defeated the CLI's built-in auto-refresh: when env var is
// present, claude reads it directly and never touches credentials.json, so
// expired access tokens were never swapped using the refreshToken sitting
// right there in the file. The result was a hard 401 cascade every few
// hours (access token TTL ≈ 8h) requiring manual keychain → PI231 copy.
//
// 2026-05-28 spike: with credentials.json present and CLAUDE_CODE_OAUTH_TOKEN
// unset, the CLI detects expired accessToken, calls the OAuth token endpoint
// with refreshToken, persists the new accessToken/expiresAt back to
// credentials.json, and proceeds. Tested on PI231 v2.1.104 with simulated
// expiry; the file's expiresAt advanced from "1 min ago" to "8 hours from
// now" in a single spawn cycle.
//
// So: DO NOT inject env.CLAUDE_CODE_OAUTH_TOKEN here. buildSpawnEnv already
// forwards process.env (minus ANTHROPIC_*), so if the operator explicitly
// set CLAUDE_CODE_OAUTH_TOKEN at OLP boot time, it's still honored — that
// path is for users who can't rely on file/keychain (e.g. ephemeral CI).
//
// The readAuthArtifact() call above is now a preflight check only — verify
// creds exist before spawn — but the value is NOT forwarded to the CLI.
//
// Authority:
// - claude CLI v2.1.104 reads ~/.claude/.credentials.json + refreshes via
// refreshToken (empirically verified PI231 2026-05-28; no public doc cite)
// - OCP server.mjs:864-888 was the historical source of the env-injection
// pattern, kept here for OAuth artifact discovery but no longer for env
// forwarding
// ADR 0009 Amendment 1: system prompt extracted from IR messages,
// prepended with OLP_SYSTEM_PROMPT_WRAPPER, passed via --system-prompt.