{"record":{"id":"f97acc0577d59a71","repo":"JuliusBrussee/caveman","slug":"agent-servername-mcp-transaction-failed-and-safe-recovery","errorCode":null,"errorMessage":"${agent} ${serverName} MCP transaction failed and safe recovery was blocked: ${(recoveryError as Error).message}; original error: ${(error as Error).message}","messagePattern":"(.+?) (.+?) MCP transaction failed and safe recovery was blocked: (.+?); original error: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":12947,"sourceCode":"      if (plan.changed) durableReplaceFileIfUnchanged(plan.path, plan.before, plan.after, plan.beforeMode);\n      if (!optionalBytesEqual(markerBefore, markerAfter)) durableReplaceFileIfUnchanged(markerPath, markerBefore, markerAfter);\n    } else {\n      if (!optionalBytesEqual(markerBefore, markerAfter)) durableReplaceFileIfUnchanged(markerPath, markerBefore, markerAfter);\n      if (plan.changed) durableReplaceFileIfUnchanged(plan.path, plan.before, plan.after, plan.beforeMode);\n    }\n    if (optionalBytesHash(fileBytes(plan.path)) !== journal.config_after_sha256\n      || optionalBytesHash(fileBytes(markerPath)) !== journal.marker_after_sha256) {\n      throw new Error(`${agent} ${serverName} MCP transaction postflight mismatch`);\n    }\n    durableUnlink(configPendingPath);\n    durableUnlink(locatorPath);\n    return true;\n  } catch (error) {\n    try {\n      const recovery = recoverOwnedMcpTransaction(readOwnedMcpConfigPending(plan.path) ?? readOwnedMcpPendingLocator(agent, serverName));\n      if (recovery === \"finalized\") return true;\n    } catch (recoveryError) {\n      throw new Error(`${agent} ${serverName} MCP transaction failed and safe recovery was blocked: ${(recoveryError as Error).message}; original error: ${(error as Error).message}`);\n    }\n    throw new Error(`${agent} ${serverName} MCP transaction failed and rolled back: ${(error as Error).message}`);\n  }\n}\n\nfunction withOwnedMcpTransactionLock<T>(\n  agent: \"kilo\" | \"qwen\",\n  serverName: string,\n  run: (lockedConfigPath: string) => T,\n): T {\n  const markerPath = canonicalOwnedMcpMarkerPath(agent, serverName);\n  return withMcpConfigLock(markerPath, () => {\n    const activeConfigPath = () => canonicalMcpConfigPath(agent === \"kilo\" ? kiloConfigPath() : qwenConfigPath());\n    const resourcePath = () => readOwnedMcpPendingLocator(agent, serverName)?.journal.config_path\n      ?? readMcpServerMarker(agent, serverName)?.config_path\n      ?? activeConfigPath();\n    for (let attempt = 0; attempt < 8; attempt++) {\n      const lockPath = canonicalMcpConfigPath(resourcePath());","sourceCodeStart":12929,"sourceCodeEnd":12965,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L12929-L12965","documentation":"Thrown when a kilo/qwen MCP config transaction (a journaled, locked multi-step write to the agent's native config) fails and the automatic recovery of the ownership journal also throws. The library combines both error messages so the developer sees the original failure and the reason safe recovery was blocked.","triggerScenarios":"Calling an MCP install/remove flow for agent 'kilo' or 'qwen' where the transaction body throws (e.g. config write fails mid-write) AND recoverOwnedMcpTransaction cannot finalize/roll back from the pending journal (corrupt or unwritable pending state).","commonSituations":"Config file permissions changed mid-transaction; journal file manually edited or truncated; disk full during recovery; concurrent processes raced outside the lock.","solutions":["Inspect the pending journal file referenced by the locator and repair/remove it so recovery can proceed","Fix the underlying recovery failure reported in the message (permissions, disk space, corrupt JSON)","Re-run the MCP install/remove command; the transaction retries cleanly once pending state is consistent","Manually restore the agent's native MCP config from a backup and delete pending state, then re-install"],"exampleFix":"// before: stale pending journal blocks recovery\nrecoverOwnedMcpTransaction(readOwnedMcpConfigPending(plan.path));\n// after: clear stale pending state before retry\nremoveOwnedMcpConfigPending(plan.path); // then re-run install\nrecoverOwnedMcpTransaction(readOwnedMcpConfigPending(plan.path));","handlingStrategy":"try-catch","validationCode":"const pending = readOwnedMcpConfigPending(plan.path) ?? readOwnedMcpPendingLocator(agent, serverName);\nif (pending) verifyPendingJournalIntegrity(pending);","typeGuard":null,"tryCatchPattern":"try { runMcpTransaction(agent, serverName) } catch (e) {\n  if (String(e).includes('safe recovery was blocked')) {\n    // inspect/clear pending journal, then retry once\n  }\n}","preventionTips":["Never hand-edit the MCP ownership journal","Keep config directories writable and monitored for disk space","Run MCP installs without concurrent agent processes"],"tags":["mcp","config","transaction","recovery"],"backgroundTag":"invalid-state-transition","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"}