JuliusBrussee/caveman · error

Hermes routing block changed after enable; refusing…

Error message

Hermes routing block changed after enable; refusing destructive disable

What it means

During Hermes config disable, the journaled routing block must still exist verbatim in the config file. If route_block is not found in the text, the file was modified after enable and the tool refuses to disable destructively, protecting user edits from being clobbered by blind text replacement.

Solutions

  1. Restore the routing block to its exact journaled content (git checkout/backup), then re-run disable.
  2. Manually delete the Caveman routing block from the Hermes config.
  3. Re-run enable to rewrite the block and refresh the journal, then disable.

Example fix

// before: whitespace changed inside block
# caveman-route-start
  route: caveman   # reformatted
# caveman-route-end
// after: exact original restored
# caveman-route-start
route: caveman
# caveman-route-end
Defensive patterns

Strategy: validation

Validate before calling

const text = fs.readFileSync(hermesConfig, 'utf8');
const rb = journal.owned?.route_block;
if (typeof rb !== 'string' || !text.includes(rb)) {
  throw new Error('Hermes routing block drift detected; restore it before disable');
}

Type guard

const routeBlockIntact = (text, owned) =>
  typeof owned?.route_block === 'string' && text.includes(owned.route_block);

Try / catch

try {
  await caveman.disable(['hermes']);
} catch (err) {
  if (err.message.includes('Hermes routing block changed')) {
    console.error('Restore the exact routing block (git checkout) or delete it manually.');
  } else throw err;
}

Prevention

When it happens

Trigger: Running disable with kind === "hermes-config" when !text.includes(routeBlock) — the routing block was edited, reformatted, commented out, or partially deleted after enable.

Common situations: User tweaked routing rules inside the Caveman block; a config formatter changed indentation/line endings; user already manually removed part of the block.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/4ee7a775110a1fc0. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:8410

    }
    text = text.replace(routeBlock, typeof previousRouteLine === "string" ? previousRouteLine : "");
    text = text.replace(readBlock, "");
    return Buffer.from(text.replace(/\n{3,}/g, "\n\n"));
  }
  if (operation.kind === "aider-core") {
    throw new Error(`${operation.file} changed after enable; refusing destructive disable`);
  }
  if (operation.kind === "hermes-plugin-manifest" || operation.kind === "hermes-plugin-init") {
    throw new Error(`${operation.file} changed after enable; refusing destructive disable`);
  }
  if (operation.kind === "hermes-config") {
    let text = current.toString("utf8");
    const routeBlock = operation.owned?.route_block;
    const previousRouteLines = operation.owned?.previous_route_lines;
    if (typeof routeBlock !== "string" || !Array.isArray(previousRouteLines)) {
      throw new Error("Hermes integration journal lacks owned routing block");
    }
    if (!text.includes(routeBlock)) throw new Error("Hermes routing block changed after enable; refusing destructive disable");
    text = text.replace(routeBlock, previousRouteLines.filter((line): line is string => typeof line === "string").join("\n"));
    for (const key of ["plugin_block", "mcp_block"] as const) {
      const block = operation.owned?.[key];
      if (block === null || block === undefined) continue;
      if (typeof block !== "string" || !text.includes(block)) throw new Error(`Hermes ${key.replace("_block", "")} block changed after enable; refusing destructive disable`);
      text = text.replace(block, "");
    }
    return Buffer.from(text);
  }
  const rootBlock = operation.owned?.root_block;
  const tablesBlock = operation.owned?.tables_block;
  if (typeof rootBlock !== "string" || typeof tablesBlock !== "string") throw new Error("Codex integration journal lacks owned blocks");
  let text = current.toString("utf8");
  for (const [block, begin] of [[rootBlock, CODEX_NATIVE_ROOT_BEGIN], [tablesBlock, CODEX_NATIVE_TABLES_BEGIN]] as const) {
    if (text.includes(begin) && !text.includes(block)) throw new Error("Codex Caveman config block changed after enable; refusing destructive disable");
    text = text.replace(`${block}\n\n`, "").replace(`\n\n${block}\n`, "\n").replace(block, "");
  }
  return Buffer.from(text);

View on GitHub (pinned to 3ee70a1026)