JuliusBrussee/caveman · error · Error
MCP transaction failed and safe recovery was blocked: …
Error message
${agent} ${serverName} MCP transaction failed and safe recovery was blocked: ${(recoveryError as Error).message}; original error: ${(error as Error).message} What it means
Thrown when a kilo/qwen MCP config transaction (a journaled, locked multi-step write to the agent's native config) fails and the automatic recovery of the ownership journal also throws. The library combines both error messages so the developer sees the original failure and the reason safe recovery was blocked.
Solutions
- Inspect the pending journal file referenced by the locator and repair/remove it so recovery can proceed
- Fix the underlying recovery failure reported in the message (permissions, disk space, corrupt JSON)
- Re-run the MCP install/remove command; the transaction retries cleanly once pending state is consistent
- Manually restore the agent's native MCP config from a backup and delete pending state, then re-install
Example fix
// before: stale pending journal blocks recovery recoverOwnedMcpTransaction(readOwnedMcpConfigPending(plan.path)); // after: clear stale pending state before retry removeOwnedMcpConfigPending(plan.path); // then re-run install recoverOwnedMcpTransaction(readOwnedMcpConfigPending(plan.path));
Defensive patterns
Strategy: try-catch
Validate before calling
const pending = readOwnedMcpConfigPending(plan.path) ?? readOwnedMcpPendingLocator(agent, serverName); if (pending) verifyPendingJournalIntegrity(pending);
Try / catch
try { runMcpTransaction(agent, serverName) } catch (e) {
if (String(e).includes('safe recovery was blocked')) {
// inspect/clear pending journal, then retry once
}
} Prevention
- Never hand-edit the MCP ownership journal
- Keep config directories writable and monitored for disk space
- Run MCP installs without concurrent agent processes
When it happens
Trigger: Calling an MCP install/remove flow for agent 'kilo' or 'qwen' where the transaction body throws (e.g. config write fails mid-write) AND recoverOwnedMcpTransaction cannot finalize/roll back from the pending journal (corrupt or unwritable pending state).
Common situations: Config file permissions changed mid-transaction; journal file manually edited or truncated; disk full during recovery; concurrent processes raced outside the lock.
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 failed and rolled back
- MCP changes require the ownership transaction
- : MCP ownership journal failed; native config may already…
- MCP config or ownership journal changed during interrupted…
- MCP transaction journal already exists; refusing overwrite
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/f97acc0577d59a71.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:12947
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,
run: (lockedConfigPath: string) => T,
): T {
const markerPath = canonicalOwnedMcpMarkerPath(agent, serverName);
return withMcpConfigLock(markerPath, () => {
const activeConfigPath = () => canonicalMcpConfigPath(agent === "kilo" ? kiloConfigPath() : qwenConfigPath());
const resourcePath = () => readOwnedMcpPendingLocator(agent, serverName)?.journal.config_path
?? readMcpServerMarker(agent, serverName)?.config_path
?? activeConfigPath();
for (let attempt = 0; attempt < 8; attempt++) {
const lockPath = canonicalMcpConfigPath(resourcePath());View on GitHub (pinned to 3ee70a1026)