JuliusBrussee/caveman · error · Error

changed during interrupted removal; refusing destructive…

Error message

${skill.file} changed during interrupted removal; refusing destructive recovery

What it means

During recovery of an interrupted agent-native bundle REMOVAL, each journaled skill file is checked to still match its post-setup hash (after_sha256) or its pre-setup content. If the file differs from both, it was modified after setup, and the removal rollback would destroy unknown content, so the CLI refuses.

Solutions

  1. Restore the skill file content to match the journal's after_sha256 (re-download or re-copy the canonical skill)
  2. Delete the pending removal journal if you accept losing the current file content
  3. Skip rollback by manually deleting the journaled files and the journal yourself
Defensive patterns

Strategy: validation

Validate before calling

for (const skill of journal.skills) {
  const cur = existsSync(skill.file) ? readFileSync(skill.file) : null;
  if (cur && createHash('sha256').update(cur).digest('hex') !== skill.after_sha256) {
    console.error(`${skill.file} differs from journaled state; resolve before removal retry.`);
  }
}

Try / catch

try {
  runSetup(agent);
} catch (e) {
  if (String(e.message).includes('changed during interrupted removal')) {
    // back up the divergent file, restore journaled content or delete journal explicitly
  }
}

Prevention

When it happens

Trigger: Running setup/removal while a pending removal journal exists and a skill file's current bytes neither hash to after_sha256 nor match the before snapshot (skillMatchesBefore).

Common situations: User edited a Caveman-installed skill file themselves, then removal was interrupted and retried; a formatter or sync tool rewrote the file between interruption and retry.

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


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/21408c8e09171ac4. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:2816

  installAgentNativeCloudMcp(agent, journal.cloud_mcp);
  installAgentNativeBundleSkills(agent, journal);
  verifyAgentNativeBundle(agent, journal, journal.cloud_mcp);
  atomicWriteFile(agentNativeBundleJournalPath(agent), Buffer.from(JSON.stringify(journal, null, 2) + "\n"));
}

function recoverPendingAgentNativeRemoval(agent: "claude" | "codex"): void {
  const pending = readPendingAgentNativeBundleRemoval(agent);
  if (!pending) return;
  if (agentNativeBundleRemovalCompleted(agent, pending)) {
    try { unlinkSync(agentNativeBundleJournalPath(agent)); } catch { /* removal already committed */ }
    unlinkSync(agentNativeBundleRemovalJournalPath(agent));
    process.stderr.write(`${mark("warn")} completed interrupted ${agent} agent-native bundle removal\n`);
    return;
  }
  for (const skill of pending.skills) {
    const current = fileBytes(skill.file);
    if (current && bytesHash(current) !== skill.after_sha256 && !skillMatchesBefore(skill)) {
      throw new Error(`${skill.file} changed during interrupted removal; refusing destructive recovery`);
    }
  }
  const marker = readMcpServerMarker(agent, "caveman-cloud");
  const markerKnown = !marker || sameMcpCommand(marker, pending.cloud_mcp) || sameMcpCommand(marker, pending.previous_cloud_mcp);
  const hostIsInstalled = agentNativeCloudMcpMatches(agent, pending.cloud_mcp);
  const hostIsPrevious = pending.previous_cloud_mcp
    ? agentNativeCloudMcpMatches(agent, pending.previous_cloud_mcp)
    : agentNativeCloudMcpHostAbsent(agent);
  if (!markerKnown || (!hostIsInstalled && !hostIsPrevious)) {
    throw new Error(`${agent} caveman-cloud MCP changed during interrupted removal; refusing destructive recovery`);
  }
  restoreInstalledAgentNativeBundle(agent, pending);
  unlinkSync(agentNativeBundleRemovalJournalPath(agent));
  process.stderr.write(`${mark("warn")} rolled back interrupted ${agent} agent-native bundle removal before continuing\n`);
}

function preflightAgentNativeSetup(agent: "claude" | "codex"): void {
  recoverPendingAgentNativeRemoval(agent);

View on GitHub (pinned to 3ee70a1026)