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

  1. Run any 'caveman mcp' command for the same agent — recovery of the interrupted transaction runs first and cleans the pending copies on success.
  2. If recovery refuses (see the recovery errors), resolve the underlying state (restore before/after content or delete both pending copies) as documented there.
  3. Confirm no other caveman process is mid-transaction before deleting anything; the files may be actively in use.
  4. 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

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


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_sha256

View on GitHub (pinned to 3ee70a1026)