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

  1. Compare the two .pending files (diff config.pending marker.pending) and determine which reflects the intended transaction.
  2. Ensure no other CLI instance is running, delete the stale/incorrect copy so only one journal remains, and re-run recovery.
  3. If intent is unclear, delete both pending journals and redo the install/uninstall transaction from scratch.
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06). Data as JSON: /api/errors/54de62892c1babca. Report an issue: GitHub.