{"record":{"id":"21408c8e09171ac4","repo":"JuliusBrussee/caveman","slug":"skill-file-changed-during-interrupted-removal","errorCode":null,"errorMessage":"${skill.file} changed during interrupted removal; refusing destructive recovery","messagePattern":"(.+?) changed during interrupted removal; refusing destructive recovery","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":2816,"sourceCode":"  installAgentNativeCloudMcp(agent, journal.cloud_mcp);\n  installAgentNativeBundleSkills(agent, journal);\n  verifyAgentNativeBundle(agent, journal, journal.cloud_mcp);\n  atomicWriteFile(agentNativeBundleJournalPath(agent), Buffer.from(JSON.stringify(journal, null, 2) + \"\\n\"));\n}\n\nfunction recoverPendingAgentNativeRemoval(agent: \"claude\" | \"codex\"): void {\n  const pending = readPendingAgentNativeBundleRemoval(agent);\n  if (!pending) return;\n  if (agentNativeBundleRemovalCompleted(agent, pending)) {\n    try { unlinkSync(agentNativeBundleJournalPath(agent)); } catch { /* removal already committed */ }\n    unlinkSync(agentNativeBundleRemovalJournalPath(agent));\n    process.stderr.write(`${mark(\"warn\")} completed interrupted ${agent} agent-native bundle removal\\n`);\n    return;\n  }\n  for (const skill of pending.skills) {\n    const current = fileBytes(skill.file);\n    if (current && bytesHash(current) !== skill.after_sha256 && !skillMatchesBefore(skill)) {\n      throw new Error(`${skill.file} changed during interrupted removal; refusing destructive recovery`);\n    }\n  }\n  const marker = readMcpServerMarker(agent, \"caveman-cloud\");\n  const markerKnown = !marker || sameMcpCommand(marker, pending.cloud_mcp) || sameMcpCommand(marker, pending.previous_cloud_mcp);\n  const hostIsInstalled = agentNativeCloudMcpMatches(agent, pending.cloud_mcp);\n  const hostIsPrevious = pending.previous_cloud_mcp\n    ? agentNativeCloudMcpMatches(agent, pending.previous_cloud_mcp)\n    : agentNativeCloudMcpHostAbsent(agent);\n  if (!markerKnown || (!hostIsInstalled && !hostIsPrevious)) {\n    throw new Error(`${agent} caveman-cloud MCP changed during interrupted removal; refusing destructive recovery`);\n  }\n  restoreInstalledAgentNativeBundle(agent, pending);\n  unlinkSync(agentNativeBundleRemovalJournalPath(agent));\n  process.stderr.write(`${mark(\"warn\")} rolled back interrupted ${agent} agent-native bundle removal before continuing\\n`);\n}\n\nfunction preflightAgentNativeSetup(agent: \"claude\" | \"codex\"): void {\n  recoverPendingAgentNativeRemoval(agent);","sourceCodeStart":2798,"sourceCodeEnd":2834,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L2798-L2834","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Restore the skill file content to match the journal's after_sha256 (re-download or re-copy the canonical skill)","Delete the pending removal journal if you accept losing the current file content","Skip rollback by manually deleting the journaled files and the journal yourself"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"for (const skill of journal.skills) {\n  const cur = existsSync(skill.file) ? readFileSync(skill.file) : null;\n  if (cur && createHash('sha256').update(cur).digest('hex') !== skill.after_sha256) {\n    console.error(`${skill.file} differs from journaled state; resolve before removal retry.`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  runSetup(agent);\n} catch (e) {\n  if (String(e.message).includes('changed during interrupted removal')) {\n    // back up the divergent file, restore journaled content or delete journal explicitly\n  }\n}","preventionTips":["Treat Caveman-installed skill files as managed: edit copies, not originals","Complete interrupted removals promptly","Version-control your custom skills outside the managed directory"],"tags":["journal-integrity","skill-files","recovery","cli"],"backgroundTag":"checksum-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}