JuliusBrussee/caveman · error · Error

MCP transaction postflight mismatch

Error message

${agent} ${serverName} MCP transaction postflight mismatch

What it means

After applying the planned config and marker writes, the CLI re-hashes both files and verifies they match the journal's recorded after-hashes (config_after_sha256 / marker_after_sha256). If they don't, the writes were skipped (durableReplaceFileIfUnchanged detected concurrent modification) or something rewrote the files mid-transaction; the transaction aborts and safe rollback is attempted.

Solutions

  1. Re-run the command: the built-in rollback runs first, and once the competing writer is gone the retried transaction should commit cleanly.
  2. Close/pause the agent and any config-sync tools while running 'caveman mcp install/uninstall'.
  3. Check write permissions and free disk space on the config directory; fix and retry.
  4. If rollback was also blocked, the CLI reports 'failed and safe recovery was blocked' — resolve recovery per the recovery-error guidance, then retry.

Example fix

// before: agent rewrites config mid-install
caveman mcp install kilo   # postflight mismatch, rolled back
// after: stop the agent, then install
pkill -f claude && caveman mcp install kilo
Defensive patterns

Strategy: try-catch

Validate before calling

import { accessSync, W_OK } from 'node:fs';
try { accessSync(configPath, W_OK); }
catch { console.error('config not writable — the transaction postflight will fail'); }
// also ensure the agent process is stopped before installing

Type guard

function agentConfigIsQuiescent(configPath: string, quietMs = 1000): Promise<boolean> {
  const s1 = statSync(configPath).mtimeMs;
  return new Promise(r => setTimeout(() => r(statSync(configPath).mtimeMs === s1), quietMs));
}

Try / catch

try {
  runMcpCommand();
} catch (e) {
  if (String(e.message).includes('transaction failed and rolled back')) {
    stopAgentProcesses();
    runMcpCommand(); // retry after rollback
  }
}

Prevention

When it happens

Trigger: Thrown in transactOwnedMcpConfig's commit phase when optionalBytesHash(fileBytes(plan.path)) !== journal.config_after_sha256 || the marker hash !== marker_after_sha256 — i.e. a conditional replace was skipped or a concurrent writer changed the files between write and verification.

Common situations: The agent application (or a config watcher) rewrote its MCP config during the transaction; a second caveman process raced the same transaction; read-only or full disk silently degraded the write; the plan's after-state was computed from stale input JSON.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/38db2d307cf94dee. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:12937

  durableCreateFile(locatorPath, pendingBytes);
  try {
    durableCreateFile(configPendingPath, pendingBytes);
  } catch (error) {
    durableUnlink(locatorPath);
    throw error;
  }

  try {
    if (action === "install") {
      if (plan.changed) durableReplaceFileIfUnchanged(plan.path, plan.before, plan.after, plan.beforeMode);
      if (!optionalBytesEqual(markerBefore, markerAfter)) durableReplaceFileIfUnchanged(markerPath, markerBefore, markerAfter);
    } else {
      if (!optionalBytesEqual(markerBefore, markerAfter)) durableReplaceFileIfUnchanged(markerPath, markerBefore, markerAfter);
      if (plan.changed) durableReplaceFileIfUnchanged(plan.path, plan.before, plan.after, plan.beforeMode);
    }
    if (optionalBytesHash(fileBytes(plan.path)) !== journal.config_after_sha256
      || optionalBytesHash(fileBytes(markerPath)) !== journal.marker_after_sha256) {
      throw new Error(`${agent} ${serverName} MCP transaction postflight mismatch`);
    }
    durableUnlink(configPendingPath);
    durableUnlink(locatorPath);
    return true;
  } catch (error) {
    try {
      const recovery = recoverOwnedMcpTransaction(readOwnedMcpConfigPending(plan.path) ?? readOwnedMcpPendingLocator(agent, serverName));
      if (recovery === "finalized") return true;
    } catch (recoveryError) {
      throw new Error(`${agent} ${serverName} MCP transaction failed and safe recovery was blocked: ${(recoveryError as Error).message}; original error: ${(error as Error).message}`);
    }
    throw new Error(`${agent} ${serverName} MCP transaction failed and rolled back: ${(error as Error).message}`);
  }
}

function withOwnedMcpTransactionLock<T>(
  agent: "kilo" | "qwen",
  serverName: string,

View on GitHub (pinned to 3ee70a1026)