mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
fix(d9): inject fake auth tokens in Suite 13f before() (CI Linux)
D9 CI failure root cause (logged in run 26332283523):
Suite 13f "Fallback engine — HTTP integration" had 3 tests fail on
Linux Node 20 runners with:
"No Anthropic OAuth token found. Run claude auth login or set
CLAUDE_CODE_OAUTH_TOKEN."
Tests pass on macOS Node 25 local because the host has a real
Anthropic OAuth entry in the macOS keychain. The anthropic plugin's
readAuthArtifact() returns that real token, the mock spawn fires,
fallback chain works.
CI Linux runners have no keychain and no ~/.claude/.credentials.json,
so readAuthArtifact() returns null and the anthropic plugin throws
ProviderError(AUTH_MISSING) BEFORE the mock spawn function gets a
chance to fire. AUTH_MISSING is NOT a hard trigger per ADR 0004
§ Decision § No fallback for client-side errors, so the chain stops
at the first hop. The 502 the client receives reports anthropic's
AUTH_MISSING, not the SPAWN_FAILED the test set up.
Three failing tests:
1. "no fallback config + mock anthropic: POST → 200 + Hops: 0"
(single-hop; anthropic auth failed before mock spawn)
2. "fallback config: anthropic SPAWN_FAILED → openai succeeds → 200
+ Hops: 1" (anthropic AUTH_MISSING stops chain; openai never
tried)
3. "fallback config: both providers SPAWN_FAILED → exhausted +
header" (anthropic AUTH_MISSING; triedProviders.length=1; no
Fallback-Exhausted header emitted)
Fix:
Suite 13f before() now injects fake auth for both anthropic + codex
for the entire suite lifetime, restoring originals in after().
process.env.CLAUDE_CODE_OAUTH_TOKEN = 'fake-anthropic-token-...'
process.env.OPENAI_CODEX_AUTH_PATH = <tempfile with fake codex
accessToken>
Auth artifact injection at suite-level (not per-test) is correct
because every test in Suite 13f needs both anthropic and codex to
pass their auth checks before mock spawn fires. The earlier
per-test codex auth injection at line 3672 was correct in pattern
but anthropic was missed.
Long comment in before() explains the failure mode so future readers
do not regress to assuming "tests pass on my Mac = tests pass on CI."
Verification:
Node 25.8.0 (Mac): 277/277 pass.
Node 20.20.2 (Mac): 277/277 pass — verified via @brew node@20 to
match CI matrix.
hygiene grep: fake tokens are clearly fake-* placeholders; no real
credentials.
Reviewer: this is the codex round-2 fold-in pattern applied to a
testing scenario — environment-specific dependency that worked
locally but not on a clean CI runner. Same evidence-first lesson:
"works on my machine ≠ works on CI." Memory entry
~/.cc-rules/memory/feedback/evidence_first_under_speed_pressure.md
should grow a corollary "tests that depend on local secrets / OS
features must inject fakes at suite level not assume host state."
Co-Authored-By: Claude Opus 4.7 (noreply@anthropic.com)
This commit is contained in:
co-authored by
Claude Opus 4.7 (noreply@anthropic.com)
parent
5960486542
commit
95f6dbd1e2
+44
-1
@@ -3545,6 +3545,9 @@ describe('Fallback engine — observability / header annotation (D9)', () => {
|
|||||||
describe('Fallback engine — HTTP integration (D9)', () => {
|
describe('Fallback engine — HTTP integration (D9)', () => {
|
||||||
let server;
|
let server;
|
||||||
let port;
|
let port;
|
||||||
|
let savedAnthropicToken;
|
||||||
|
let savedCodexAuthPath;
|
||||||
|
let suiteCodexAuthFile;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
__resetSpawnImpl();
|
__resetSpawnImpl();
|
||||||
@@ -3552,6 +3555,29 @@ describe('Fallback engine — HTTP integration (D9)', () => {
|
|||||||
mistralResetSpawnImpl();
|
mistralResetSpawnImpl();
|
||||||
__resetFallbackConfig();
|
__resetFallbackConfig();
|
||||||
|
|
||||||
|
// CRITICAL — inject suite-level fake auth tokens for anthropic + codex.
|
||||||
|
// Without this, on hosts that lack the real auth artifacts (CI Linux
|
||||||
|
// runners are the load-bearing case; they have no macOS keychain and no
|
||||||
|
// ~/.claude/.credentials.json), the provider plugins throw
|
||||||
|
// ProviderError(AUTH_MISSING) BEFORE the mock spawn function fires. The
|
||||||
|
// chain then stops at the first hop because AUTH_MISSING is NOT a hard
|
||||||
|
// trigger (per ADR 0004 § Decision § No fallback for client-side
|
||||||
|
// errors), so the fallback path under test never executes. Bug
|
||||||
|
// discovered on D9 CI 2026-05-23: 3 tests in this suite failed on
|
||||||
|
// Node 20 Linux while passing on macOS Node 25 because the macOS
|
||||||
|
// keychain provided real anthropic auth and the test never reached
|
||||||
|
// the fake-auth-required code path.
|
||||||
|
savedAnthropicToken = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
||||||
|
process.env.CLAUDE_CODE_OAUTH_TOKEN = 'fake-anthropic-token-for-d9-http-suite';
|
||||||
|
|
||||||
|
const { writeFileSync } = await import('node:fs');
|
||||||
|
const { tmpdir } = await import('node:os');
|
||||||
|
const { join: pathJoin } = await import('node:path');
|
||||||
|
suiteCodexAuthFile = pathJoin(tmpdir(), `olp-test-d9-codex-auth-${Date.now()}.json`);
|
||||||
|
writeFileSync(suiteCodexAuthFile, JSON.stringify({ accessToken: 'fake-codex-token-for-d9-http-suite' }), 'utf8');
|
||||||
|
savedCodexAuthPath = process.env.OPENAI_CODEX_AUTH_PATH;
|
||||||
|
process.env.OPENAI_CODEX_AUTH_PATH = suiteCodexAuthFile;
|
||||||
|
|
||||||
// Start test server with all 3 providers enabled
|
// Start test server with all 3 providers enabled
|
||||||
const testProviders = loadProviders({ enabled: { anthropic: true, openai: true, mistral: true } });
|
const testProviders = loadProviders({ enabled: { anthropic: true, openai: true, mistral: true } });
|
||||||
const { loadedProviders: lp, cacheStore: cs } = await import('./server.mjs');
|
const { loadedProviders: lp, cacheStore: cs } = await import('./server.mjs');
|
||||||
@@ -3575,11 +3601,28 @@ describe('Fallback engine — HTTP integration (D9)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
after(() => {
|
after(async () => {
|
||||||
__resetFallbackConfig();
|
__resetFallbackConfig();
|
||||||
__resetSpawnImpl();
|
__resetSpawnImpl();
|
||||||
codexResetSpawnImpl();
|
codexResetSpawnImpl();
|
||||||
mistralResetSpawnImpl();
|
mistralResetSpawnImpl();
|
||||||
|
|
||||||
|
// Restore env vars injected by before()
|
||||||
|
if (savedAnthropicToken !== undefined) {
|
||||||
|
process.env.CLAUDE_CODE_OAUTH_TOKEN = savedAnthropicToken;
|
||||||
|
} else {
|
||||||
|
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
||||||
|
}
|
||||||
|
if (savedCodexAuthPath !== undefined) {
|
||||||
|
process.env.OPENAI_CODEX_AUTH_PATH = savedCodexAuthPath;
|
||||||
|
} else {
|
||||||
|
delete process.env.OPENAI_CODEX_AUTH_PATH;
|
||||||
|
}
|
||||||
|
if (suiteCodexAuthFile) {
|
||||||
|
const { unlinkSync } = await import('node:fs');
|
||||||
|
try { unlinkSync(suiteCodexAuthFile); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
if (!server) return;
|
if (!server) return;
|
||||||
return new Promise(r => server.close(r));
|
return new Promise(r => server.close(r));
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user