JuliusBrussee/caveman · error · Error
MCP transaction journal already exists; refusing overwrite
Error message
${agent} ${serverName} MCP transaction journal already exists; refusing overwrite What it means
Before starting a transaction the CLI creates two pending-journal copies, and it refuses to overwrite either if they already exist. A leftover journal means a previous transaction never finished being recovered; blindly overwriting it would orphan that transaction's rollback data.
Solutions
- Run any 'caveman mcp' command for the same agent — recovery of the interrupted transaction runs first and cleans the pending copies on success.
- If recovery refuses (see the recovery errors), resolve the underlying state (restore before/after content or delete both pending copies) as documented there.
- Confirm no other caveman process is mid-transaction before deleting anything; the files may be actively in use.
- After clearing stale journals, verify the agent config is in the desired state and re-run the install/uninstall.
Example fix
// before: leftover journal blocks new transaction error: ... MCP transaction journal already exists; refusing overwrite // after: trigger recovery (reads and clears pending state) caveman mcp list kilo && caveman mcp install kilo
Defensive patterns
Strategy: validation
Validate before calling
import { existsSync } from 'node:fs';
const stale = [markerPath + '.pending', configPath + '.pending'].filter(existsSync);
if (stale.length) {
console.warn('pending journal exists — run a caveman mcp command to trigger recovery first:', stale.join(', '));
} Type guard
function noPendingJournal(markerPath: string, configPath: string): boolean {
return !existsSync(markerPath + '.pending') && !existsSync(configPath + '.pending');
} Try / catch
try {
runMcpCommand();
} catch (e) {
if (String(e.message).includes('transaction journal already exists')) {
// any mcp subcommand for the agent runs recovery first and clears the copies on success
runAnyMcpCommand(agent); // e.g. list/show
runMcpCommand();
}
} Prevention
- After a crash or forced abort, run recovery via any caveman mcp command before installing again
- Don't bypass recovery prompts; resolving them clears the pending state cleanly
- Check for leftover .pending files after abnormal terminations (kill -9, OOM, power loss)
- Run caveman mcp operations to completion; avoid closing terminals mid-transaction
When it happens
Trigger: Thrown in transactOwnedMcpConfig when fileBytes(locatorPath) !== null || fileBytes(configPendingPath) !== null — either '<markerPath>.pending' or the config-adjacent pending journal already exists when a new install/uninstall is planned.
Common situations: A previous run crashed before recovery could complete and the pending files remain; recovery was skipped because the user answered no/aborted; a previous run threw the 'refusing destructive recovery' errors (500-504) leaving journals in place; stale files from an old CLI version at the same paths.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- MCP transaction journals disagree; refusing recovery
- : MCP ownership journal failed; native config may already…
- MCP config or ownership journal changed during interrupted…
- MCP transaction failed and safe recovery was blocked: …
- MCP transaction lost one durable journal copy; refusing…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/fe285da33eda9809.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:12917
server_name: serverName,
action,
config_path: plan.path,
marker_path: markerPath,
config_before_base64: plan.before?.toString("base64") ?? null,
config_before_mode: plan.beforeMode,
config_before_sha256: optionalBytesHash(plan.before),
config_after_sha256: optionalBytesHash(plan.after),
marker_before_base64: markerBefore?.toString("base64") ?? null,
marker_before_mode: markerBeforeMode,
marker_before_sha256: optionalBytesHash(markerBefore),
marker_after_base64: markerAfter?.toString("base64") ?? null,
marker_after_sha256: optionalBytesHash(markerAfter),
};
const locatorPath = `${markerPath}.pending`;
const configPendingPath = mcpConfigPendingJournalPath(plan.path);
const pendingBytes = Buffer.from(JSON.stringify(journal, null, 2) + "\n");
if (fileBytes(locatorPath) !== null || fileBytes(configPendingPath) !== null) {
throw new Error(`${agent} ${serverName} MCP transaction journal already exists; refusing overwrite`);
}
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_sha256View on GitHub (pinned to 3ee70a1026)