{"record":{"id":"e954652c373dc3fc","repo":"JuliusBrussee/caveman","slug":"codex-caveman-config-block-changed-after-enable-r","errorCode":null,"errorMessage":"Codex Caveman config block changed after enable; refusing destructive disable","messagePattern":"Codex Caveman config block changed after enable; refusing destructive disable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":7730,"sourceCode":"    if (typeof routeBlock !== \"string\" || !Array.isArray(previousRouteLines)) {\n      throw new Error(\"Hermes integration journal lacks owned routing block\");\n    }\n    if (!text.includes(routeBlock)) throw new Error(\"Hermes routing block changed after enable; refusing destructive disable\");\n    text = text.replace(routeBlock, previousRouteLines.filter((line): line is string => typeof line === \"string\").join(\"\\n\"));\n    for (const key of [\"plugin_block\", \"mcp_block\"] as const) {\n      const block = operation.owned?.[key];\n      if (block === null || block === undefined) continue;\n      if (typeof block !== \"string\" || !text.includes(block)) throw new Error(`Hermes ${key.replace(\"_block\", \"\")} block changed after enable; refusing destructive disable`);\n      text = text.replace(block, \"\");\n    }\n    return Buffer.from(text);\n  }\n  const rootBlock = operation.owned?.root_block;\n  const tablesBlock = operation.owned?.tables_block;\n  if (typeof rootBlock !== \"string\" || typeof tablesBlock !== \"string\") throw new Error(\"Codex integration journal lacks owned blocks\");\n  let text = current.toString(\"utf8\");\n  for (const [block, begin] of [[rootBlock, CODEX_NATIVE_ROOT_BEGIN], [tablesBlock, CODEX_NATIVE_TABLES_BEGIN]] as const) {\n    if (text.includes(begin) && !text.includes(block)) throw new Error(\"Codex Caveman config block changed after enable; refusing destructive disable\");\n    text = text.replace(`${block}\\n\\n`, \"\").replace(`\\n\\n${block}\\n`, \"\\n\").replace(block, \"\");\n  }\n  return Buffer.from(text);\n}\n\nfunction writeNativeRestoration(file: string, bytes: Buffer | null): void {\n  if (bytes) {\n    atomicWriteFile(file, bytes);\n    return;\n  }\n  try { unlinkSync(file); } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n  }\n}\n\nfunction restoreNativeJournalFiles(journal: NativeJournal): Array<{ file: string; bytes: Buffer | null }> {\n  // Resolve every merge/conflict before first write. A conflict therefore leaves\n  // all host files and the journal byte-identical.","sourceCodeStart":7712,"sourceCodeEnd":7748,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/2f49f0e1a352aa810e70056b7930aeb0b3d219b4/packages/cli/src/index.ts#L7712-L7748","documentation":"Thrown while disabling or repairing Codex when config.toml drifted after enable and, for either owned block, the Caveman begin marker ('# >>> caveman:native-root' or '# >>> caveman:native-tables') is still present in the file but the exact journaled block content is not. Caveman strips its blocks verbatim on disable; if the content between the markers was edited, a blind removal could delete your edits, so it refuses. Note the asymmetry: if the whole marker is absent, the block is treated as already removed and the disable proceeds.","triggerScenarios":"Editing lines inside the Caveman-managed blocks of ~/.codex/config.toml after `caveman enable codex` — e.g. changing the model provider baseURL or profile tables Caveman wrote — then running `caveman disable codex` or `caveman doctor codex --fix`.","commonSituations":"Tweaking the routed model or provider settings that live inside the caveman block; another codex config tool rewriting config.toml with different formatting; resolving a git dotfiles conflict by editing inside the block.","solutions":["Restore the exact block text from owned.root_block / owned.tables_block of ~/.caveman/integrations/codex.json (keep everything between the markers identical), then rerun `caveman disable codex`","Run `caveman doctor codex` first to confirm which block drifted","If you want to keep the edits: move them out of the caveman blocks first, then disable; or clean up manually and delete the journal","Re-run `caveman enable codex` and disable in the same session before any edits"],"exampleFix":"# before: ~/.codex/config.toml, caveman block edited after enable\n# >>> caveman:native-root\nmodel = 'gpt-5-mini'   # changed from journaled value\n\n# after: block matches owned.root_block exactly, then `caveman disable codex` succeeds","handlingStrategy":"try-catch","validationCode":"// Marker present but content changed is the exact trigger — check both\nimport { readFileSync } from 'node:fs';\nconst home = process.env.CAVEMAN_HOME ?? `${process.env.HOME}/.caveman`;\nconst j = JSON.parse(readFileSync(`${home}/integrations/codex.json`, 'utf8'));\nconst text = readFileSync(`${process.env.HOME}/.codex/config.toml`, 'utf8');\nfor (const [block, begin] of [['root_block', '# >>> caveman:native-root'], ['tables_block', '# >>> caveman:native-tables']]) {\n  const owned = j.operations[0]?.owned?.[block];\n  if (text.includes(begin) && !text.includes(owned)) console.error(`${block} drifted`);\n}","typeGuard":null,"tryCatchPattern":"catch (err) { if (err.message.includes('Codex Caveman config block changed after enable')) { /* restore the exact block text between the caveman markers, or move your edits outside the markers first; retry once */ } else throw err; }","preventionTips":["Make config.toml edits outside the '# >>> caveman:native-*' marker blocks only","Run `caveman doctor codex` before scripted disable to see drift","Re-run `caveman enable codex` after intentional edits to re-stamp the journal"],"tags":["codex","config-drift","toml","routing","disable"],"backgroundTag":"config-drift-detected","analyzedSha":"2f49f0e1a352aa810e70056b7930aeb0b3d219b4","analyzedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}