{"record":{"id":"e55ae937fbc3f3c0","repo":"JuliusBrussee/caveman","slug":"skill-file-changed-after-setup-refusing-destru","errorCode":null,"errorMessage":"${skill.file} changed after setup; refusing destructive bundle removal","messagePattern":"(.+?) changed after setup; refusing destructive bundle removal","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":2930,"sourceCode":"    throw new Error(`${agent} caveman-cloud MCP registration failed exact postflight`);\n  }\n  for (const skill of journal.skills) {\n    const current = fileBytes(skill.file);\n    if (!current || bytesHash(current) !== skill.after_sha256) throw new Error(`${skill.file} failed skill postflight`);\n  }\n}\n\nfunction removeAgentNativeBundle(agent: \"claude\" | \"codex\"): void {\n  recoverPendingAgentNativeRemoval(agent);\n  recoverPendingAgentNativeBundle(agent);\n  const journal = readAgentNativeBundleJournal(agent);\n  if (!journal) {\n    process.stderr.write(`${mark(\"warn\")} ${agent}: no agent-native bundle journal found\\n`);\n    return;\n  }\n  for (const skill of journal.skills) {\n    const current = fileBytes(skill.file);\n    if (!current || bytesHash(current) !== skill.after_sha256) throw new Error(`${skill.file} changed after setup; refusing destructive bundle removal`);\n  }\n  const marker = readMcpServerMarker(agent, \"caveman-cloud\");\n  if (!sameMcpCommand(marker, journal.cloud_mcp) || !agentNativeCloudMcpMatches(agent, journal.cloud_mcp)) {\n    throw new Error(`${agent} caveman-cloud MCP changed after setup; refusing destructive bundle removal`);\n  }\n  if (journal.native_owned) {\n    const nativeJournal = readNativeJournal(agent);\n    if (nativeJournal) {\n      for (const operation of nativeJournal.operations) restoreNativeOperation(operation);\n    }\n  }\n  atomicWriteFile(agentNativeBundleRemovalJournalPath(agent), Buffer.from(JSON.stringify(journal, null, 2) + \"\\n\"));\n  try {\n    restoreAgentNativeBundleSkills(journal.skills);\n    restoreAgentNativeCloudMcp(agent, journal.previous_cloud_mcp);\n    if (journal.native_owned) disableNativeAgent(agent);\n    unlinkSync(agentNativeBundleJournalPath(agent));\n    unlinkSync(agentNativeBundleRemovalJournalPath(agent));","sourceCodeStart":2912,"sourceCodeEnd":2948,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L2912-L2948","documentation":"Thrown by removeAgentNativeBundle as a safety interlock: before destructively removing the agent-native bundle, every journaled skill file is re-hashed. If any file is missing or its hash differs from the journal's after_sha256, removal aborts because the bundle's contents were modified after setup and rollback/restore could destroy user changes.","triggerScenarios":"Running `caveman setup --agent-native <agent> --remove` when a skill file delivered by the bundle has been edited, deleted, or replaced since setup — fileBytes returns null or the hash differs from journal.skills[].after_sha256.","commonSituations":"User customized a delivered skill file; a sync tool or editor rewrote the file; agent upgrade changed skill contents; manual cleanup deleted a skill file.","solutions":["If the edits are intentional, back them up, run --remove (or restore originals), then re-apply changes to a non-managed location.","Restore the skill files to their setup-time content so hashes match the journal, then re-run --remove.","Delete the stale journal (agent-native bundle journal path) only if you accept that the bundle will not be auto-rolled back, and remove files manually.","Re-run setup to refresh the journal hashes to the current file contents, then remove."],"exampleFix":"// before: removing with locally edited skill\n$ caveman setup --agent-native claude --remove\nError: .../SKILL.md changed after setup; refusing destructive bundle removal\n// after: preserve the edit first\n$ cp ~/.claude/skills/caveman/SKILL.md ~/backup/SKILL.md\n$ git -C ~/.claude/skills/caveman checkout -- SKILL.md\n$ caveman setup --agent-native claude --remove","handlingStrategy":"validation","validationCode":"for (const skill of journal.skills) {\n  const current = fileBytes(skill.file);\n  if (!current || bytesHash(current) !== skill.after_sha256) {\n    console.error(`Back up changes to ${skill.file} before running --remove`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  removeAgentNativeBundle(agent);\n} catch (err) {\n  if (/changed after setup/.test((err as Error).message)) {\n    console.error(\"Bundle files were customized; back them up, then re-run --remove\");\n  }\n}","preventionTips":["Treat bundle-delivered skill files as managed: don't edit them in place.","Keep local customizations in a separate, non-managed location.","Re-run setup (refreshing journal hashes) after intentional edits, then remove."],"tags":["safety-interlock","removal","integrity","skills","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"}