JuliusBrussee/caveman · error · Error

Hermes ${key.replace("_block", "")} block changed after enab

Error message

Hermes ${key.replace("_block", "")} block changed after enable; refusing destructive disable

What it means

During Hermes disable, for each optional owned block recorded in the journal (plugin_block or mcp_block — the message renders the key without the '_block' suffix, so it reads 'plugin' or 'mcp'), Caveman verifies the exact block text is still present before removing it. If the journal has the block but the file drifted and the string is gone or was changed (or the journal value is not a string), the disable refuses as destructive.

Source

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

  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);
}

function writeNativeRestoration(file: string, bytes: Buffer | null): void {
  if (bytes) {
    atomicWriteFile(file, bytes);

View on GitHub (pinned to 2f49f0e1a3)

Solutions

  1. Restore the exact block text from owned.plugin_block / owned.mcp_block of ~/.caveman/integrations/hermes.json, then rerun `caveman disable hermes`
  2. If you intentionally removed those blocks: complete the manual uninstall (remove remaining caveman blocks and the journal file) instead of re-running disable
  3. Re-run `caveman enable hermes` then disable in one session

Example fix

# before: mcp block edited -> 'Hermes mcp block changed after enable; refusing destructive disable'
# after: restore owned.mcp_block verbatim, then `caveman disable hermes`
Defensive patterns

Strategy: try-catch

Validate before calling

import { readFileSync } from 'node:fs';
const home = process.env.CAVEMAN_HOME ?? `${process.env.HOME}/.caveman`;
const j = JSON.parse(readFileSync(`${home}/integrations/hermes.json`, 'utf8'));
const op = j.operations.find((o) => o.kind === 'hermes-config');
let text = readFileSync(op.file, 'utf8');
for (const key of ['plugin_block', 'mcp_block']) {
  const block = op.owned?.[key];
  if (block != null && (typeof block !== 'string' || !text.includes(block))) {
    console.error(`${key} drifted — restore it before disable`);
  }
}

Try / catch

catch (err) { if (/Hermes (plugin|mcp) block changed after enable/.test(err.message)) { /* restore the named block from the journal, or complete the manual uninstall and delete the journal */ } else throw err; }

Prevention

When it happens

Trigger: `caveman disable hermes` after the Hermes plugin registration or MCP registration lines inside the config were edited or deleted post-enable, while hermes-plugin files and routing stayed journaled.

Common situations: Removing the caveman MCP lines by hand but leaving the rest of the integration; editing plugin paths; partial manual uninstalls followed by an attempted managed disable.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@2f49f0e1a3 (2026-08-18). Data as JSON: /api/errors/d06ab257ebc04684. Report an issue: GitHub.