JuliusBrussee/caveman · error · Error
${agent} ${serverName} MCP transaction journals disagree; re
Error message
${agent} ${serverName} MCP transaction journals disagree; refusing recovery What it means
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.
Source
Thrown at packages/cli/src/index.ts:12450
const canonicalPath = canonicalMcpConfigPath(configPath);
return readOwnedMcpPendingJournalAt(mcpConfigPendingJournalPath(canonicalPath), { configPath: canonicalPath });
}
type OwnedMcpRecovery = "none" | "discarded" | "finalized" | "rolled-back";
function recoverOwnedMcpTransaction(pending: ReadOwnedMcpPendingJournal | null): OwnedMcpRecovery {
if (!pending) return "none";
const { journal, configBefore, markerBefore } = pending;
const agent = journal.agent;
const serverName = journal.server_name;
const configPath = journal.config_path;
const markerPath = journal.marker_path;
const configPendingPath = mcpConfigPendingJournalPath(configPath);
const locatorPath = `${markerPath}.pending`;
const configPending = readOwnedMcpPendingJournalAt(configPendingPath, { agent, serverName, configPath });
const locatorPending = readOwnedMcpPendingJournalAt(locatorPath, { agent, serverName, configPath, locatorPath });
if (configPending && locatorPending && !configPending.bytes.equals(locatorPending.bytes)) {
throw new Error(`${agent} ${serverName} MCP transaction journals disagree; refusing recovery`);
}
const configCurrent = fileBytes(configPath);
const markerCurrent = fileBytes(markerPath);
const configHash = optionalBytesHash(configCurrent);
const markerHash = optionalBytesHash(markerCurrent);
const configIsBefore = configHash === journal.config_before_sha256;
const configIsAfter = configHash === journal.config_after_sha256;
const markerIsBefore = markerHash === journal.marker_before_sha256;
const markerIsAfter = markerHash === journal.marker_after_sha256;
const removePendingCopies = () => {
if (configPending) durableUnlink(configPendingPath);
if (locatorPending) durableUnlink(locatorPath);
};
if (configIsAfter && markerIsAfter) {
removePendingCopies();
process.stderr.write(`${mark("warn")} finalized interrupted ${agent} ${serverName} MCP transaction\n`);
return "finalized";View on GitHub (pinned to 5184b3d11a)
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.
Example fix
// before diff ~/.claude.json.pending ~/.claude/marker.pending # contents differ // after rm ~/.claude/marker.pending && kilo mcp recover # keep the authoritative copy
Defensive patterns
Strategy: validation
Validate before calling
const a = fs.readFileSync(cfgPending), b = fs.readFileSync(markerPending);
if (a.equals(b) === false) { /* resolve which copy is authoritative before recovery */ } Try / catch
try { recoverPending(); } catch (e) {
if (e.message.includes('journals disagree')) { /* diff the .pending files, remove the stale copy or both, then redo */ }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cave_subagent_concurrency_limit_invalid
- cave_subagent_concurrency_limit
- native session key read: %w
- integration change already running for ${agent}
- ${path} changed during MCP update; refusing overwrite
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06).
Data as JSON: /api/errors/54de62892c1babca.
Report an issue: GitHub.