{"record":{"id":"c8a6ac36de0d64ac","repo":"JuliusBrussee/caveman","slug":"cave-harness-upstream-version-mismatch-c8a6ac","errorCode":null,"errorMessage":"cave_harness_upstream_version_mismatch","messagePattern":"cave_harness_upstream_version_mismatch","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/claude-runtime.ts","lineNumber":247,"sourceCode":"    };\n\n    const startedAt = performance.now();\n    const query = (options.queryFn ?? claudeQuery)({ prompt: input, options: sdkOptions });\n    let initVersion: string | undefined;\n    let assistantModel: string | undefined;\n    let credentialRegime: ClaudeCredentialRegime = \"unknown\";\n    let result: SDKResultMessage | undefined;\n    const toolCalls: string[] = [];\n    try {\n      for await (const message of query) {\n        if (message.type === \"system\" && message.subtype === \"init\") {\n          initVersion = message.claude_code_version;\n          // The exact-pin is enforced at the FIRST message the SDK emits, before\n          // it drives any model call, so a version mismatch costs nothing rather\n          // than being caught only after the whole run has drained and spent\n          // `finally` closes the query.\n          if (initVersion !== CLAUDE_CODE_VERSION) {\n            throw new Error(\"cave_harness_upstream_version_mismatch\");\n          }\n          credentialRegime = claudeCredentialRegime(message.apiKeySource);\n          // apiKeySource is emitted on init before the SDK drives a model call.\n          // It is the credential the SDK actually selected, unlike ambient env\n          // presence. A subscription or unknown regime cannot authorize a USD\n          // cap because no per-token dollar charge is proven.\n          if (options.maxBudgetUsd !== undefined && credentialRegime !== \"metered\") {\n            throw new Error(\"cave_budget_denomination_unavailable\");\n          }\n          assistantModel ??= message.model;\n        }\n        if (message.type === \"assistant\") {\n          assistantModel = message.message.model;\n          for (const block of message.message.content) {\n            if (block.type === \"tool_use\") toolCalls.push(unprefixClaudeTool(block.name));\n          }\n        }\n        if (message.type === \"result\") result = message;","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/claude-runtime.ts#L229-L265","documentation":"The Claude Agent SDK stream emitted an init message whose claude_code_version does not equal the framework's exact pin (CLAUDE_CODE_VERSION, currently \"2.1.220\" in runtime-identity.ts). The framework exact-pins the upstream harness so tool-calling semantics and message shapes stay reproducible; a drifted CLI breaks that contract. The check fires on the first SDK message, before any model call, so a mismatch costs zero tokens. It is thrown from inside the message loop and surfaces after the finally block closes the query.","triggerScenarios":"Running an agent whose resolved `claude` CLI executable reports a version other than 2.1.220 on its init system message. Typical causes: a locally installed @anthropic-ai/claude-code of a different version resolved by PATH, an updated global CLI after a framework upgrade (or vice versa), or a PATH option/env pointing at a pinned SDK install that drifted.","commonSituations":"Upgrading the caveman framework without reinstalling its pinned Claude Code CLI (or the reverse), CI images caching an older CLI, nvm/PATH resolution picking a user-global claude binary, or a teammate's lockfile-less install resolving a newer @anthropic-ai/claude-code.","solutions":["Install the exact pinned version: npm i -g @anthropic-ai/claude-code@2.1.220 (or pin it in devDependencies) and confirm `claude --version`.","Check which CLI the SDK resolves (PATH, CLAUDE_CODE_ENTRYPOINT, or the path option passed to the SDK) and point it at the 2.1.220 install.","If the framework itself is outdated relative to the CLI, upgrade the caveman agent package so its CLAUDE_CODE_VERSION pin matches the CLI you must run.","Verify in CI by asserting `claude --version` equals the value exported from runtime-identity.ts before running builds."],"exampleFix":"// before: ambient CLI resolution, version can drift\nconst query = queryFn({ ...options });\n\n// after: pin the executable the SDK launches\nconst query = queryFn({\n  ...options,\n  pathToClaudeCodeExecutable: \"/opt/caveman/claude-2.1.220/claude\",\n});","handlingStrategy":"validation","validationCode":"import { execFileSync } from \"node:child_process\";\nimport { CLAUDE_CODE_VERSION } from \"@caveman/agent\"; // or read runtime-identity\nfunction assertClaudeVersion(pathToClaude = \"claude\"): void {\n  const out = execFileSync(pathToClaude, [\"--version\"], { encoding: \"utf8\" }).trim();\n  if (!out.includes(CLAUDE_CODE_VERSION)) {\n    throw new Error(`CLI ${out} != pinned ${CLAUDE_CODE_VERSION}; install @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await run(options);\n} catch (error) {\n  if (error instanceof Error && error.message === \"cave_harness_upstream_version_mismatch\") {\n    // environment defect, not transient: fix the CLI pin before retrying\n    throw new Error(`Claude CLI must be ${CLAUDE_CODE_VERSION}; see caveman-agent doctor`);\n  }\n  throw error;\n}","preventionTips":["Pin @anthropic-ai/claude-code to the exact version from runtime-identity.ts in every environment (package.json devDependency, CI image, Dockerfile).","Add a CI preflight step asserting `claude --version` matches the pin before any caveman build/dev run.","When upgrading the caveman agent package, diff the exported CLAUDE_CODE_VERSION and reinstall the CLI in the same change."],"tags":["claude-sdk","version-pinning","harness","determinism"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}