{"record":{"id":"c2dcaeb2f7d028e6","repo":"JuliusBrussee/caveman","slug":"hermes-integration-journal-lacks-owned-routing-blo","errorCode":null,"errorMessage":"Hermes integration journal lacks owned routing block","messagePattern":"Hermes integration journal lacks owned routing block","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":7713,"sourceCode":"    if (!text.includes(routeBlock) || !text.includes(readBlock)) {\n      throw new Error(\"Aider Caveman config block changed after enable; refusing destructive disable\");\n    }\n    text = text.replace(routeBlock, typeof previousRouteLine === \"string\" ? previousRouteLine : \"\");\n    text = text.replace(readBlock, \"\");\n    return Buffer.from(text.replace(/\\n{3,}/g, \"\\n\\n\"));\n  }\n  if (operation.kind === \"aider-core\") {\n    throw new Error(`${operation.file} changed after enable; refusing destructive disable`);\n  }\n  if (operation.kind === \"hermes-plugin-manifest\" || operation.kind === \"hermes-plugin-init\") {\n    throw new Error(`${operation.file} changed after enable; refusing destructive disable`);\n  }\n  if (operation.kind === \"hermes-config\") {\n    let text = current.toString(\"utf8\");\n    const routeBlock = operation.owned?.route_block;\n    const previousRouteLines = operation.owned?.previous_route_lines;\n    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, \"\");","sourceCodeStart":7695,"sourceCodeEnd":7731,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/2f49f0e1a352aa810e70056b7930aeb0b3d219b4/packages/cli/src/index.ts#L7695-L7731","documentation":"The Hermes integration journal (~/.caveman/integrations/hermes.json) exists but its owned metadata is malformed for the hermes-config operation: route_block is not a string or previous_route_lines is not an array. Those fields hold the exact routing block Caveman inserted and the lines it replaced; without them the disable cannot restore the original config, so it aborts. This indicates a journal written by an incompatible Caveman version or one that was truncated/edited.","triggerScenarios":"`caveman disable hermes` / `caveman doctor hermes --fix` with a journal whose operations[].owned lacks a string route_block or an array previous_route_lines — after a version downgrade, partial write, or manual edit of the journal file.","commonSituations":"Rolling Caveman back to a version with a different journal schema; a crashed enable leaving a half-written journal; hand-editing the integrations directory.","solutions":["Re-run `caveman enable hermes` to write a fresh journal against the live config, then `caveman disable hermes`","Upgrade to the Caveman version that performed the enable and disable with it","Manual path: delete the caveman routing/plugin/mcp blocks from the Hermes config, then remove ~/.caveman/integrations/hermes.json and .pending-hermes.json"],"exampleFix":"# before: malformed journal\n caveman disable hermes\n# >> Hermes integration journal lacks owned routing block\n\n# after: rewrite journal, then disable\ncaveman enable hermes && caveman disable hermes","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'node:fs';\nconst home = process.env.CAVEMAN_HOME ?? `${process.env.HOME}/.caveman`;\nconst j = JSON.parse(readFileSync(`${home}/integrations/hermes.json`, 'utf8'));\nconst valid = j.operations.every((op) =>\n  op.kind !== 'hermes-config' ||\n  (typeof op.owned?.route_block === 'string' && Array.isArray(op.owned?.previous_route_lines)));\nif (!valid) console.error('run `caveman enable hermes` to rewrite the journal before disable');","typeGuard":"const hasHermesOwnedRouting = (op) =>\n  typeof op.owned?.route_block === 'string' && Array.isArray(op.owned?.previous_route_lines);","tryCatchPattern":"catch (err) { if (err.message.includes('journal lacks owned routing block')) { /* re-enable to rewrite journal, then disable */ } else throw err; }","preventionTips":["Do not edit or partially copy ~/.caveman/integrations/hermes.json","Use matching caveman versions for enable and disable"],"tags":["hermes","journal","schema","disable"],"backgroundTag":"schema-validation-failed","analyzedSha":"2f49f0e1a352aa810e70056b7930aeb0b3d219b4","analyzedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}