{"record":{"id":"54de62892c1babca","repo":"JuliusBrussee/caveman","slug":"agent-servername-mcp-transaction-journals-di","errorCode":null,"errorMessage":"${agent} ${serverName} MCP transaction journals disagree; refusing recovery","messagePattern":"(.+?) (.+?) MCP transaction journals disagree; refusing recovery","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":12450,"sourceCode":"  const canonicalPath = canonicalMcpConfigPath(configPath);\n  return readOwnedMcpPendingJournalAt(mcpConfigPendingJournalPath(canonicalPath), { configPath: canonicalPath });\n}\n\ntype OwnedMcpRecovery = \"none\" | \"discarded\" | \"finalized\" | \"rolled-back\";\n\nfunction recoverOwnedMcpTransaction(pending: ReadOwnedMcpPendingJournal | null): OwnedMcpRecovery {\n  if (!pending) return \"none\";\n  const { journal, configBefore, markerBefore } = pending;\n  const agent = journal.agent;\n  const serverName = journal.server_name;\n  const configPath = journal.config_path;\n  const markerPath = journal.marker_path;\n  const configPendingPath = mcpConfigPendingJournalPath(configPath);\n  const locatorPath = `${markerPath}.pending`;\n  const configPending = readOwnedMcpPendingJournalAt(configPendingPath, { agent, serverName, configPath });\n  const locatorPending = readOwnedMcpPendingJournalAt(locatorPath, { agent, serverName, configPath, locatorPath });\n  if (configPending && locatorPending && !configPending.bytes.equals(locatorPending.bytes)) {\n    throw new Error(`${agent} ${serverName} MCP transaction journals disagree; refusing recovery`);\n  }\n  const configCurrent = fileBytes(configPath);\n  const markerCurrent = fileBytes(markerPath);\n  const configHash = optionalBytesHash(configCurrent);\n  const markerHash = optionalBytesHash(markerCurrent);\n  const configIsBefore = configHash === journal.config_before_sha256;\n  const configIsAfter = configHash === journal.config_after_sha256;\n  const markerIsBefore = markerHash === journal.marker_before_sha256;\n  const markerIsAfter = markerHash === journal.marker_after_sha256;\n  const removePendingCopies = () => {\n    if (configPending) durableUnlink(configPendingPath);\n    if (locatorPending) durableUnlink(locatorPath);\n  };\n\n  if (configIsAfter && markerIsAfter) {\n    removePendingCopies();\n    process.stderr.write(`${mark(\"warn\")} finalized interrupted ${agent} ${serverName} MCP transaction\\n`);\n    return \"finalized\";","sourceCodeStart":12432,"sourceCodeEnd":12468,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L12432-L12468","documentation":"Thrown when both the config-side and marker-side pending journals exist for the same transaction but their raw bytes differ (configPending.bytes.equals(locatorPending.bytes) is false). Two divergent copies of the journal make the true transaction state ambiguous, so recovery refuses rather than guess.","triggerScenarios":"Recovering a transaction where <config>.pending and <markerPath>.pending both exist with different contents — e.g. one was partially rewritten by a second concurrent transaction, or a tool updated one copy and crashed before updating the other.","commonSituations":"Two CLI processes running MCP installs concurrently for the same server; a crashed run that wrote only one of the paired journals before dying; manual editing of one journal copy.","solutions":["Compare the two .pending files (diff config.pending marker.pending) and determine which reflects the intended transaction.","Ensure no other CLI instance is running, delete the stale/incorrect copy so only one journal remains, and re-run recovery.","If intent is unclear, delete both pending journals and redo the install/uninstall transaction from scratch.","Avoid concurrent MCP transactions for the same agent/serverName; serialize them."],"exampleFix":"// before\ndiff ~/.claude.json.pending ~/.claude/marker.pending  # contents differ\n// after\nrm ~/.claude/marker.pending && kilo mcp recover  # keep the authoritative copy","handlingStrategy":"validation","validationCode":"const a = fs.readFileSync(cfgPending), b = fs.readFileSync(markerPending);\nif (a.equals(b) === false) { /* resolve which copy is authoritative before recovery */ }","typeGuard":null,"tryCatchPattern":"try { recoverPending(); } catch (e) {\n  if (e.message.includes('journals disagree')) { /* diff the .pending files, remove the stale copy or both, then redo */ }\n}","preventionTips":["Never run concurrent MCP transactions for the same agent/serverName.","Write both paired journals atomically (temp file + rename) so they can't diverge.","After a crash, delete both pending journals together and redo the transaction if in doubt."],"tags":["journal-conflict","concurrency","recovery-refused"],"backgroundTag":"conflicting-state-detected","analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-09-06T12:00:26.372Z","contentChangedAt":"2026-09-06T12:00:26.372Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}