{"record":{"id":"fa45e9eddc3bd6aa","repo":"JuliusBrussee/caveman","slug":"agent-servername-mcp-transaction-state-kept-changing","errorCode":null,"errorMessage":"${agent} ${serverName} MCP transaction state kept changing; refusing mutation","messagePattern":"(.+?) (.+?) MCP transaction state kept changing; refusing mutation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":12983,"sourceCode":"      const lockPath = canonicalMcpConfigPath(resourcePath());\n      const outcome = withMcpConfigLock<{ retry: true } | { retry: false; value: T }>(lockPath, () => {\n        const locatorPending = readOwnedMcpPendingLocator(agent, serverName);\n        if (locatorPending) {\n          if (locatorPending.journal.config_path !== lockPath) return { retry: true };\n          recoverOwnedMcpTransaction(locatorPending);\n          return { retry: true };\n        }\n        const configPending = readOwnedMcpConfigPending(lockPath);\n        if (configPending) {\n          recoverOwnedMcpTransaction(configPending);\n          return { retry: true };\n        }\n        if (canonicalMcpConfigPath(resourcePath()) !== lockPath) return { retry: true };\n        return { retry: false, value: run(lockPath) };\n      });\n      if (!outcome.retry) return outcome.value;\n    }\n    throw new Error(`${agent} ${serverName} MCP transaction state kept changing; refusing mutation`);\n  });\n}\n\nfunction mcpInstall(target?: string, serverName = \"caveman\"): number {\n  if (serverName !== \"caveman\" && serverName !== \"caveman-browse\" && serverName !== \"caveman-cloud\" && serverName !== \"caveman-delegate\") {\n    console.error(`unknown MCP server '${serverName}'. valid: caveman, caveman-browse, caveman-cloud, caveman-delegate`);\n    process.exit(2);\n  }\n  let mcp: { command: string; args: string[] };\n  if (serverName === \"caveman\") {\n    mcp = resolveMcpCommand();\n  } else if (serverName === \"caveman-browse\") {\n    const binary = resolveGoBin(\"caveman-browse\", \"CAVEMAN_BROWSE_BIN\");\n    if (!binary) {\n      console.error(\"caveman-browse binary not found — run `caveman setup --install` first\");\n      process.exit(1);\n    }\n    mcp = { command: binary, args: [] };","sourceCodeStart":12965,"sourceCodeEnd":13001,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L12965-L13001","documentation":"A safety guard in withOwnedMcpTransactionLock: the lock retry loop kept observing changing transaction state (different lock paths / canonical config paths) after exhausting retries, so it refuses to mutate rather than corrupt the config.","triggerScenarios":"Running an MCP install/remove for kilo/qwen where canonicalMcpConfigPath(resourcePath()) returns a different lockPath than the one acquired across repeated attempts, exhausting the retry budget.","commonSituations":"resourcePath() (home dir / config root) moving between attempts; symlinked or remapped config directories; environment changes (HOME, XDG vars) mid-process.","solutions":["Ensure HOME/config environment variables are stable for the process lifetime","Check that the agent's config path is not a symlink chain that resolves differently per attempt","Re-run the command once; transient path instability may have resolved","Report a bug if the path is stable — this indicates a canonicalization defect"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const before = canonicalMcpConfigPath(resourcePath());\nawait sleep(50);\nif (canonicalMcpConfigPath(resourcePath()) !== before) throw new Error('unstable config path');","typeGuard":null,"tryCatchPattern":"try { installMcp(...) } catch (e) {\n  if (String(e).includes('kept changing')) { /* stabilize env, retry once */ }\n}","preventionTips":["Do not mutate HOME/XDG env vars while the CLI runs","Avoid symlinked config roots that resolve inconsistently"],"tags":["mcp","lock","concurrency","config"],"backgroundTag":"internal-invariant-violation","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}