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
- Open the Hermes config, find the model: section, and keep exactly one provider: and one base_url: line
- 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
- Keep exactly one provider: and one base_url: line under model:
- Deduplicate after dotfile merges before running installers
- Lint the Hermes YAML for duplicate keys
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
- Aider config has duplicate openai-api-base keys; refusing un
- Aider config has duplicate read keys; refusing unsafe merge
- Hermes plugins.enabled uses inline YAML; refusing unsafe nat
- Aider config uses inline/scalar read; use block-list form be
- existing Hermes Caveman native block is unjournaled; run `ca
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/9e449e168cb14b05.
Report an issue: GitHub.