JuliusBrussee/caveman · error

Hermes model provider/base_url keys are duplicated; refusing

Error message

Hermes model provider/base_url keys are duplicated; refusing ambiguous install

What it means

hermesNativeConfig() replaces existing `provider:`/`base_url:` keys inside the top-level `model:` section with its own marked block. It first collects those lines; if normalizing the key names yields duplicates (e.g. provider listed twice), removal order and which value wins become ambiguous, so it refuses the install.

Source

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

  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");
  }
  const insertAt = routeIndexes[0] ?? model.start + 1;
  for (const index of [...routeIndexes].sort((a, b) => b - a)) lines.splice(index, 1);
  const routeBlock = [
    `  ${HERMES_NATIVE_ROUTE_BEGIN}`,
    '  provider: "custom"',
    `  base_url: ${yamlQuote(appendUrlPath(gw, "/w/hermes"))}`,
    `  ${HERMES_NATIVE_ROUTE_END}`,
  ];
  lines.splice(insertAt, 0, ...routeBlock);

  let pluginBlock: string | null = null;
  const plugins = topLevelSection(lines, "plugins");
  if (plugins) {
    let enabled = -1;
    for (let i = plugins.start + 1; i < plugins.end; i++) {
      if (/^  enabled:\s*\[/.test(lines[i]!)) throw new Error("Hermes plugins.enabled uses inline YAML; refusing unsafe native merge");
      if (/^  enabled:\s*(?:#.*)?$/.test(lines[i]!)) { enabled = i; break; }

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Open the Hermes config, find the model: section, and keep exactly one provider: and one base_url: line
  2. Re-run the caveman hermes install

Example fix

# before
model:
  provider: "openai"
  provider: "custom"
  base_url: "https://old.example"

# after
model:
  provider: "openai"
  base_url: "https://old.example"
Defensive patterns

Strategy: validation

Validate before calling

function hermesModelKeysUnique(lines: string[], section: {start: number, end: number}): boolean {
  const names = [];
  for (let i = section.start + 1; i < section.end; i++) {
    const m = lines[i]!.match(/^  ([^:]+):/);
    if (m && /^(provider|base_url):/.test(lines[i]!)) names.push(m[1]);
  }
  return new Set(names).size === names.length;
}

Try / catch

try { nativeInstallHermes(); } catch (e) {
  if (e instanceof Error && /provider\/base_url keys are duplicated/.test(e.message)) {
    dedupeHermesModelKeys(); nativeInstallHermes();
  } else throw e;
}

Prevention

When it happens

Trigger: Native Hermes install when the `model:` section contains the same key twice among provider/base_url (e.g. two ` provider:` lines, or provider plus base_url plus a second provider).

Common situations: Append-style edits to the Hermes config; pasted examples onto existing config; merge conflicts leaving duplicate keys.

Related errors


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