Fix Claude test credential setup

This commit is contained in:
Mohamed Boudra
2025-12-24 23:31:31 +07:00
parent 9d3b45475a
commit 5ce9be1688
4 changed files with 46 additions and 16 deletions

View File

@@ -0,0 +1,20 @@
# Claude test failures: real root cause
## Finding
The Claude E2E tests were not failing due to missing credentials in general; they were failing because the tests overwrite `CLAUDE_CONFIG_DIR` with a fresh temp directory and do **not** carry forward Claude's credential store. This removes the SDK's `.credentials.json` file when the SDK is running in plaintext storage mode (common in CI), so the SDK cannot load OAuth/API credentials even though the user has valid credentials in the default config directory.
## Evidence
- The Claude agent SDK stores credentials in a file named `.credentials.json` under its config directory:
- `node_modules/@anthropic-ai/claude-agent-sdk/cli.js` shows the plaintext credential store path as `join(storageDir, ".credentials.json")`.
- The tests set `process.env.CLAUDE_CONFIG_DIR` to a temp dir in:
- `packages/server/src/server/agent/providers/claude-agent.test.ts` (via `useTempClaudeConfigDir`)
- `packages/server/src/server/agent/agent-mcp.e2e.test.ts`
- The temp config dir is populated with settings only, so the SDK can't find `.credentials.json` after the override.
## Fix summary
- Copy `.credentials.json` from the original config directory (explicit `CLAUDE_CONFIG_DIR` or `~/.claude`) into the temp config dir used by tests.
- Remove the hardcoded env-var-only credential precheck so tests fail with the *real* SDK error if credentials are genuinely absent.
## Files updated
- `packages/server/src/server/agent/providers/claude-agent.test.ts`
- `packages/server/src/server/agent/agent-mcp.e2e.test.ts`

View File

@@ -2,7 +2,7 @@ import net from "node:net";
import os from "node:os";
import path from "node:path";
import { existsSync } from "node:fs";
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { copyFile, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { describe, expect, test } from "vitest";
import { experimental_createMCPClient } from "ai";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
@@ -36,6 +36,14 @@ const CLAUDE_SETTINGS = {
},
};
async function copyClaudeCredentials(sourceDir: string, targetDir: string): Promise<void> {
const sourceCredentials = path.join(sourceDir, ".credentials.json");
if (!existsSync(sourceCredentials)) {
return;
}
await copyFile(sourceCredentials, path.join(targetDir, ".credentials.json"));
}
async function getAvailablePort(): Promise<number> {
return new Promise((resolve, reject) => {
const server = net.createServer();
@@ -145,10 +153,13 @@ describe("agent MCP end-to-end", () => {
process.env.CODEX_SESSION_DIR = codexSessionDir;
process.env.CODEX_HOME = codexHome;
const previousClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR;
const sourceClaudeConfigDir =
previousClaudeConfigDir ?? path.join(os.homedir(), ".claude");
const claudeConfigDir = await mkdtemp(path.join(os.tmpdir(), "claude-config-"));
const claudeSettingsText = `${JSON.stringify(CLAUDE_SETTINGS, null, 2)}\n`;
await writeFile(path.join(claudeConfigDir, "settings.json"), claudeSettingsText, "utf8");
await writeFile(path.join(claudeConfigDir, "settings.local.json"), claudeSettingsText, "utf8");
await copyClaudeCredentials(sourceClaudeConfigDir, claudeConfigDir);
process.env.CLAUDE_CONFIG_DIR = claudeConfigDir;
const daemon = await createPaseoDaemon(daemonConfig);

View File

@@ -2,6 +2,7 @@ import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
import { createServer as createHTTPServer } from "http";
import { randomUUID } from "node:crypto";
import {
copyFileSync,
existsSync,
mkdtempSync,
readFileSync,
@@ -34,17 +35,6 @@ import { AgentManager } from "../agent-manager.js";
import { AgentRegistry } from "../agent-registry.js";
import { createAgentMcpServer } from "../mcp-server.js";
const hasClaudeCredentials = Boolean(
process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY?.trim()?.length
);
const requireClaudeCredentials = () => {
if (!hasClaudeCredentials) {
throw new Error(
"Claude credentials missing. Set CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY to run ClaudeAgentClient integration tests."
);
}
};
function tmpCwd(): string {
const dir = mkdtempSync(path.join(os.tmpdir(), "claude-agent-e2e-"));
try {
@@ -54,8 +44,18 @@ function tmpCwd(): string {
}
}
function copyClaudeCredentials(sourceDir: string, targetDir: string): void {
const sourceCredentials = path.join(sourceDir, ".credentials.json");
if (!existsSync(sourceCredentials)) {
return;
}
copyFileSync(sourceCredentials, path.join(targetDir, ".credentials.json"));
}
function useTempClaudeConfigDir(): () => void {
const previousConfigDir = process.env.CLAUDE_CONFIG_DIR;
const sourceConfigDir =
previousConfigDir ?? path.join(os.homedir(), ".claude");
const configDir = mkdtempSync(path.join(os.tmpdir(), "claude-config-"));
const settings = {
permissions: {
@@ -72,6 +72,7 @@ function useTempClaudeConfigDir(): () => void {
const settingsText = `${JSON.stringify(settings, null, 2)}\n`;
writeFileSync(path.join(configDir, "settings.json"), settingsText, "utf8");
writeFileSync(path.join(configDir, "settings.local.json"), settingsText, "utf8");
copyClaudeCredentials(sourceConfigDir, configDir);
process.env.CLAUDE_CONFIG_DIR = configDir;
return () => {
if (previousConfigDir === undefined) {
@@ -274,9 +275,6 @@ describe("ClaudeAgentClient (SDK integration)", () => {
},
});
beforeAll(() => {
requireClaudeCredentials();
});
beforeAll(() => {
restoreClaudeConfigDir = useTempClaudeConfigDir();
});

View File

@@ -325,7 +325,7 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- All tests must run unconditionally and pass
- **Done (2025-12-24 23:21)**: WHAT: tightened Claude permission parity setup/cleanup and sandbox config in `packages/server/src/server/agent/providers/claude-agent.test.ts:259` and `packages/server/src/server/agent/providers/claude-agent.test.ts:495`, enabled Claude user+project settings in `packages/server/src/server/agent/providers/claude-agent.ts:536`, set CLAUDE test sandbox config in `packages/server/src/server/agent/agent-mcp.e2e.test.ts:26`, restored wait_for_agent completion via stream events in `packages/server/src/server/agent/agent-manager.ts:696`, and sanitized permission payloads for MCP responses in `packages/server/src/server/agent/mcp-server.ts:81` + `packages/server/src/server/agent/mcp-server.ts:263`. RESULT: Claude permission allow/deny/abort flows resolve correctly and MCP agent run completes without timeouts; create_agent no longer fails schema validation. EVIDENCE: `npm run test --workspace=@paseo/server -- claude-agent.test.ts -t "permission flow parity"` (3/3 passed) and `npm run test --workspace=@paseo/server -- agent-mcp.e2e.test.ts` (1/1 passed).
- [ ] **Fix**: Claude credentials work - find REAL reason Claude tests fail.
- [x] **Fix**: Claude credentials work - find REAL reason Claude tests fail.
- **Context**: You (the agent reading this) ARE Codex. The test runs Codex MCP which launches Claude.
- "Missing Claude credentials" is FALSE - verified with `scripts/codex-claude-test.ts`:
@@ -337,6 +337,7 @@ Build a new Codex MCP provider sidebyside with the existing Codex SDK prov
- So why do the E2E tests claim credentials are missing?
- Investigate what the Claude E2E tests are ACTUALLY doing differently
- Run the failing test with debug output and capture exact error
- **Done (2025-12-24 23:30)**: WHAT: copied Claude SDK `.credentials.json` into temp config dirs in `packages/server/src/server/agent/providers/claude-agent.test.ts:47` and `packages/server/src/server/agent/agent-mcp.e2e.test.ts:39`, removed the env-only credential gate in `packages/server/src/server/agent/providers/claude-agent.test.ts:55`, and documented the root cause in `REPORT-claude-credentials-failure.md`. RESULT: Claude tests no longer falsely report missing credentials when auth is stored in the default config dir. EVIDENCE: `npm run test --workspace=@paseo/server -- agent-mcp.e2e.test.ts` (1/1 passed) and `npm run test --workspace=@paseo/server -- claude-agent.test.ts -t "responds with text"` (1/1 passed; remaining tests skipped by filter).
- [x] **Test (E2E) CRITICAL**: Interruption/abort latency for Codex MCP provider.