JuliusBrussee/caveman · error
Hermes model provider/base_url keys are duplicated…
Error message
Hermes model provider/base_url keys are duplicated; refusing ambiguous install
What it means
When installing the Hermes native route block, the installer collects existing `provider:` and `base_url:` lines under the `model:` section so it can replace them. If the same key appears more than once in that section, the effective value is ambiguous (duplicates by key name), so the installer refuses the install rather than produce an ambiguous config.
Solutions
- Edit the Hermes config's `model:` section so `provider:` and `base_url:` each appear exactly once with your desired values.
- Re-run the Caveman enable command after de-duplicating the keys.
- Run `caveman doctor hermes` first if a stale Caveman route block contributed the duplicates.
Example fix
// before model: provider: "anthropic" base_url: https://api.anthropic.com provider: "openai" // after model: provider: "anthropic" base_url: https://api.anthropic.com
Defensive patterns
Strategy: validation
Validate before calling
const lines = readFileSync(hermesCfg, 'utf8').split('\n');
const keys = lines.flatMap((l, i) => /^ (provider|base_url):/.test(l) ? [l.match(/^ ([^:]+):/)[1]] : []);
if (new Set(keys).size !== keys.length) {
// dedupe provider/base_url keys in the model: section before enabling
} Try / catch
try {
await cavemanEnableHermes();
} catch (e) {
if (String(e.message).includes('duplicated')) {
dedupeModelKeys(hermesCfg, ['provider', 'base_url']);
await cavemanEnableHermes();
}
} Prevention
- Keep exactly one `provider:` and one `base_url:` line inside the `model:` section.
- Replace existing values in place instead of appending duplicates.
- Resolve merge conflicts by deduplicating YAML keys.
- Lint the Hermes YAML for duplicate keys before enabling.
When it happens
Trigger: Running the Hermes enable flow when the `model:` section of the Hermes YAML config contains duplicate `provider:` or duplicate `base_url:` keys (e.g. two `provider:` lines at the two-space indent level).
Common situations: Hand-edited configs where a provider/base_url pair was appended while the original remained; merge conflicts keeping both lines; pasted snippets from docs duplicating existing keys.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- Aider config has duplicate openai-api-base keys; refusing…
- Aider config has duplicate read keys; refusing unsafe merge
- Aider config uses inline/scalar read; use block-list form…
- auth_token: in is ignored — the inbound token is read only…
- existing Hermes Caveman native block is unjournaled; run…
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/9e449e168cb14b05.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:7747
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/v1"))}`,
` ${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 3ee70a1026)