JuliusBrussee/caveman · error · Error
MCP rollback postflight mismatch
Error message
${agent} ${serverName} MCP rollback postflight mismatch What it means
After rolling back an interrupted MCP transaction (replacing the config and/or marker with their recorded before-state), the CLI re-hashes both files and verifies they now equal the journal's before hashes. If the postflight check fails, the rollback did not actually take effect (or something concurrently rewrote the files), and the error is thrown rather than silently claiming recovery succeeded.
Solutions
- Re-run the caveman command: recovery is re-attempted on next transaction and may succeed once the concurrent writer is gone.
- Ensure no agent/editor/sync daemon is holding or auto-saving the MCP config while running recovery; pause watchers and retry.
- Check file permissions on the config and marker paths (durableReplaceFileIfUnchanged needs write access); fix with chmod/chown and retry.
- As a last resort, manually restore config and marker from the journal's base64 before-snapshots, then delete both pending copies.
Example fix
// before: recovery races an editor auto-saving the config // after: close the agent/editor, then re-run caveman mcp install kilo # retries recovery atomically
Defensive patterns
Strategy: retry
Validate before calling
import { statSync } from 'node:fs';
// ensure we can write before triggering recovery
try { accessSync(configPath, W_OK); accessSync(markerPath, W_OK); }
catch { console.error('fix permissions on config and marker before recovery'); } Type guard
function canRecoverSafely(configPath: string, markerPath: string): boolean {
try { accessSync(configPath, W_OK); accessSync(markerPath, W_OK); return true; }
catch { return false; }
} Try / catch
try {
runMcpCommand();
} catch (e) {
if (String(e.message).includes('rollback postflight mismatch')) {
stopConcurrentWriters(); // pause agents/watchers/sync
runMcpCommand(); // recovery retries on next run
}
} Prevention
- Pause agents, editors, and sync daemons that touch MCP configs while running caveman mcp
- Ensure the CLI user owns the config and marker files with write permission
- Avoid network mounts for agent configs where rename/replace semantics are unreliable
- Re-run the command after a mismatch — recovery is designed to be re-attempted
When it happens
Trigger: recoverOwnedMcpTransaction: thrown at the postflight when optionalBytesHash(fileBytes(configPath)) !== journal.config_before_sha256 or the marker hash !== marker_before_sha256 after durableReplaceFileIfUnchanged calls — i.e. the conditional replace skipped because the file changed underneath, or a concurrent writer modified files between restore and verification.
Common situations: Another process (the agent itself, a config watcher, sync software) rewrote the config during recovery; the durableReplaceFileIfUnchanged guard detected a mid-restore change and skipped; filesystem caching/quirks on network mounts; permissions preventing the rewrite.
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
- MCP transaction postflight mismatch
- MCP config target changed while acquiring lock; refusing…
- MCP transaction failed and rolled back
- changed while planning MCP update; refusing overwrite
- pending transaction is malformed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/fb743f2ce580058f.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:12861
process.stderr.write(`${mark("warn")} discarded uncommitted ${agent} ${serverName} MCP transaction\n`);
return "discarded";
}
if ((!configIsBefore && !configIsAfter) || (!markerIsBefore && !markerIsAfter)) {
throw new Error(`${agent} ${serverName} MCP config or ownership journal changed during interrupted transaction; refusing destructive recovery`);
}
if (!configPending || !locatorPending) {
throw new Error(`${agent} ${serverName} MCP transaction lost one durable journal copy; refusing destructive recovery`);
}
if (!configIsBefore) {
durableReplaceFileIfUnchanged(configPath, configCurrent, configBefore, journal.config_before_mode);
}
if (!markerIsBefore) {
durableReplaceFileIfUnchanged(markerPath, markerCurrent, markerBefore, journal.marker_before_mode);
}
if (optionalBytesHash(fileBytes(configPath)) !== journal.config_before_sha256
|| optionalBytesHash(fileBytes(markerPath)) !== journal.marker_before_sha256) {
throw new Error(`${agent} ${serverName} MCP rollback postflight mismatch`);
}
removePendingCopies();
process.stderr.write(`${mark("warn")} rolled back interrupted ${agent} ${serverName} MCP transaction\n`);
return "rolled-back";
}
function transactOwnedMcpConfig(
agent: "kilo" | "qwen",
serverName: string,
action: "install" | "uninstall",
mcp?: { command: string; args: string[] },
lockedConfigPath?: string,
): boolean {
const plan = action === "install"
? agent === "kilo" ? planMcpKiloJson(mcp!, serverName) : planMcpQwenJson(mcp!, serverName)
: agent === "kilo" ? planRemoveMcpKiloJson(serverName) : planRemoveMcpQwenJson(serverName);
if (!plan) return false;
if (lockedConfigPath && plan.path !== canonicalMcpConfigPath(lockedConfigPath)) {View on GitHub (pinned to 3ee70a1026)