JuliusBrussee/caveman · critical · Error
MCP config or ownership journal changed during interrupted…
Error message
${agent} ${serverName} MCP config or ownership journal changed during interrupted transaction; refusing destructive recovery What it means
During recovery of an interrupted MCP transaction, the CLI hashes the current config file and ownership marker and compares against the journal's before/after SHA-256 values. If either file matches NEITHER its before nor after hash, it was modified by something other than this transaction (or advanced past it), so a destructive rollback would corrupt live state and is refused.
Solutions
- Inspect the current config and marker files; if the current content is correct, delete both pending journal copies so recovery becomes a no-op.
- If you want the pre-transaction state, restore the config and marker to the journal's recorded before state (the before bytes are base64 inside the journal) and re-run to let recovery discard.
- If the after-state is what you want, set the files to the journal's after state so recovery can finalize and clean the pending copies.
- Never hand-edit while a .pending journal exists; finish or clear transactions before modifying configs.
Example fix
// before: config edited while .pending journal exists // config hash matches neither before nor after // after: clear journals once the live files are confirmed good rm <config>.pending <marker>.pending
Defensive patterns
Strategy: validation
Validate before calling
import { existsSync } from 'node:fs';
const hasPending = existsSync(markerPath + '.pending') || existsSync(configPath + '.pending');
if (hasPending) {
console.warn('pending MCP transaction exists — resolve recovery BEFORE editing the agent config');
} Type guard
function isSafeToEditConfig(configPath: string, markerPath: string): boolean {
return !existsSync(markerPath + '.pending') && !existsSync(configPath + '.pending');
} Try / catch
try {
runMcpCommand();
} catch (e) {
if (String(e.message).includes('refusing destructive recovery')) {
// confirm which state you want, restore config+marker to it, then delete both pending copies
restoreFromBackup(configPath, markerPath);
rmBothPending(markerPath, configPath);
}
} Prevention
- Never edit an agent's MCP config while a .pending journal exists
- Disable agent config auto-save/watchers during caveman mcp operations
- Resolve interrupted transactions promptly via any caveman mcp command
- Keep backups of agent configs so you can restore a known-good state
When it happens
Trigger: recoverOwnedMcpTransaction: thrown when (!configIsBefore && !configIsAfter) || (!markerIsBefore && !markerIsAfter) — the agent config file or the ownership marker currently on disk matches neither the journal's recorded before-hash nor after-hash.
Common situations: The user (or the agent) edited the MCP config after the transaction was interrupted; another tool rewrote the config with reformatted JSON; a second caveman transaction ran and changed state; clock-skewed manual restores replaced the files.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- MCP transaction journals disagree; refusing recovery
- MCP transaction lost one durable journal copy; refusing…
- MCP transaction failed and safe recovery was blocked: …
- MCP transaction journal already exists; refusing overwrite
- pending transaction is malformed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/5533bfd120cc3315.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:12847
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";
}
if (configIsBefore && markerIsBefore) {
removePendingCopies();
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";View on GitHub (pinned to 3ee70a1026)