JuliusBrussee/caveman · error

existing Hermes Caveman native block is unjournaled; run `ca

Error message

existing Hermes Caveman native block is unjournaled; run `caveman doctor hermes`

What it means

hermesNativeConfig() stamps managed blocks into the Hermes YAML config delimited by HERMES_NATIVE_ROUTE/PLUGIN/MCP BEGIN/END markers. If any of these markers is already present in the source while no journal covers it (fresh install path), the file contains a Caveman block Caveman cannot account for, so it refuses to merge.

Source

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

  end: string,
  blockLines: string[],
): string | null {
  const section = topLevelSection(lines, sectionName);
  if (section) {
    if (sectionHasChildKey(lines, section, childName)) return null;
    lines.splice(section.start + 1, 0, ...blockLines);
    return blockLines.join("\n");
  }
  if (lines.length > 0 && lines[lines.length - 1]!.trim()) lines.push("");
  const body = blockLines.filter((line) => !line.includes(begin) && !line.includes(end));
  const whole = [begin, `${sectionName}:`, ...body, end];
  lines.push(...whole);
  return whole.join("\n");
}

function hermesNativeConfig(source: string, gw: string, mcpBinary: string): { text: string; owned: Record<string, unknown> } {
  for (const marker of [HERMES_NATIVE_ROUTE_BEGIN, HERMES_NATIVE_ROUTE_END, HERMES_NATIVE_PLUGIN_BEGIN, HERMES_NATIVE_PLUGIN_END, HERMES_NATIVE_MCP_BEGIN, HERMES_NATIVE_MCP_END]) {
    if (source.includes(marker)) throw new Error("existing Hermes Caveman native block is unjournaled; run `caveman doctor hermes`");
  }
  const lines = yamlLines(source);
  let model = topLevelSection(lines, "model");
  if (!model) {
    lines.unshift("model:");
    model = topLevelSection(lines, "model")!;
  }
  const routeIndexes: number[] = [];
  const previousRouteLines: string[] = [];
  for (let i = model.start + 1; i < model.end; i++) {
    if (/^  (provider|base_url):/.test(lines[i]!)) {
      routeIndexes.push(i);
      previousRouteLines.push(lines[i]!);
    }
  }
  if (new Set(routeIndexes.map((index) => lines[index]!.match(/^  ([^:]+):/)?.[1])).size !== routeIndexes.length) {
    throw new Error("Hermes model provider/base_url keys are duplicated; refusing ambiguous install");
  }

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Run `caveman doctor hermes` to reconcile the unjournaled block
  2. Or delete every HERMES_NATIVE marker-delimited block (BEGIN through END inclusive) from the Hermes config, then retry
  3. Keep ~/.caveman state and agent configs in sync when restoring from backups

Example fix

# before — hermes config carries orphan markers
# <<< CAVEMAN NATIVE ROUTE BEGIN <<<
  provider: "custom"
  base_url: "http://127.0.0.1:8080/w/hermes"
# <<< CAVEMAN NATIVE ROUTE END <<<

# after — block removed (or `caveman doctor hermes`), then install re-stamps journaled blocks
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from "node:fs";
const HERMES_MARKERS = [HERMES_NATIVE_ROUTE_BEGIN, HERMES_NATIVE_ROUTE_END, HERMES_NATIVE_PLUGIN_BEGIN, HERMES_NATIVE_PLUGIN_END, HERMES_NATIVE_MCP_BEGIN, HERMES_NATIVE_MCP_END];
function hermesConfigClean(path: string): boolean {
  try { const src = readFileSync(path, "utf8"); return !HERMES_MARKERS.some((m) => src.includes(m)); }
  catch { return true; }
}

Try / catch

try { nativeInstallHermes(); } catch (e) {
  if (e instanceof Error && /unjournaled/.test(e.message)) {
    spawnSync("caveman", ["doctor", "hermes"], { stdio: "inherit" });
    nativeInstallHermes();
  } else throw e;
}

Prevention

When it happens

Trigger: Native Hermes install when the Hermes config file already contains any HERMES_NATIVE_* BEGIN/END marker string (e.g. restored from backup, or journal under ~/.caveman was wiped).

Common situations: Restoring the Hermes config from backups or dotfiles after resetting ~/.caveman; reusing a config from another machine; remnants of a previous install.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/4ba193ca3c54f70d. Report an issue: GitHub.