JuliusBrussee/caveman · error · Error

Codex Caveman config block changed after enable; refusing de

Error message

Codex Caveman config block changed after enable; refusing destructive disable

What it means

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.

Source

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

    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);
    return;
  }
  try { unlinkSync(file); } catch (error) {
    if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
  }
}

function restoreNativeJournalFiles(journal: NativeJournal): Array<{ file: string; bytes: Buffer | null }> {
  // Resolve every merge/conflict before first write. A conflict therefore leaves
  // all host files and the journal byte-identical.

View on GitHub (pinned to 2f49f0e1a3)

Solutions

  1. 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`
  2. Run `caveman doctor codex` first to confirm which block drifted
  3. 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
  4. Re-run `caveman enable codex` and disable in the same session before any edits

Example fix

# before: ~/.codex/config.toml, caveman block edited after enable
# >>> caveman:native-root
model = 'gpt-5-mini'   # changed from journaled value

# after: block matches owned.root_block exactly, then `caveman disable codex` succeeds
Defensive patterns

Strategy: try-catch

Validate before calling

// Marker present but content changed is the exact trigger — check both
import { readFileSync } from 'node:fs';
const home = process.env.CAVEMAN_HOME ?? `${process.env.HOME}/.caveman`;
const j = JSON.parse(readFileSync(`${home}/integrations/codex.json`, 'utf8'));
const text = readFileSync(`${process.env.HOME}/.codex/config.toml`, 'utf8');
for (const [block, begin] of [['root_block', '# >>> caveman:native-root'], ['tables_block', '# >>> caveman:native-tables']]) {
  const owned = j.operations[0]?.owned?.[block];
  if (text.includes(begin) && !text.includes(owned)) console.error(`${block} drifted`);
}

Try / catch

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

Prevention

When it happens

Trigger: 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`.

Common situations: 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.

Related errors


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