{"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":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":2610,"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":2592,"sourceCodeEnd":2628,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L2592-L2628","documentation":"Safety gate in removeAgentNativeBundle (--remove): before destructively uninstalling, each journaled skill file must still hash to after_sha256. If a skill file is missing or was modified since setup, removal aborts rather than overwrite/lose user changes layered on top of the installed skills.","triggerScenarios":"Running `caveman setup --agent-native <agent> --remove` after a skill file in the journal was edited, deleted, or rewritten by the agent or the user.","commonSituations":"User customized an installed SKILL.md; the agent updated its own skill files; sync conflicts changed file contents since the bundle was installed.","solutions":["If you intentionally modified the skills, back them up, restore them to the installed content (or delete them knowingly), then re-run --remove","Reinstall the bundle (`caveman setup --agent-native <agent>`) to reset skill files to journaled hashes, then immediately run --remove","Locate the offending path from the error message and diff it against the installed version before deciding to discard changes"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// before --remove: verify journaled skills are unmodified\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nfor (const skill of journal.skills) {\n  let bytes: Buffer;\n  try { bytes = readFileSync(skill.file); } catch { throw new Error(`${skill.file} missing; removal will abort`); }\n  if (createHash(\"sha256\").update(bytes).digest(\"hex\") !== skill.after_sha256) {\n    throw new Error(`${skill.file} modified; back it up or reinstall before --remove`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await setup([\"--agent-native\", agent, \"--remove\"]);\n} catch (error) {\n  if (/refusing destructive bundle removal/.test((error as Error).message) && /failed skill postflight|changed after setup/.test((error as Error).message)) {\n    // back up the named file, reinstall bundle to reset hashes, then remove\n    await backupAndReset(skillFileFromMessage(error));\n    await setup([\"--agent-native\", agent, \"--remove\"]);\n  } else throw error;\n}","preventionTips":["Treat installed skill files as read-only; keep customizations in separate files","Reinstall the bundle right before removing it to guarantee hash alignment","Back up the agent skills directory before destructive operations"],"tags":["removal","safety-gate","skills","destructive"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}