ruvnet/ruflo · error · Error
Duplicate route name: ${r.name}
Error message
Duplicate route name: ${r.name} What it means
Thrown by loadPolicy() after the per-entry validation passes, during a deduplication sweep that tracks each route's name in a Set. Routes are selected by name elsewhere (resolveRouteModels does routes.find(r => r.name === routeName)), so non-unique names would make routing ambiguous; the loader refuses to proceed instead.
Source
Thrown at ruflo/src/ruvocal/src/lib/server/router/policy.ts:21
import type { Route } from "./types";
let ROUTES: Route[] = [];
let loaded = false;
export async function loadPolicy(): Promise<Route[]> {
const path = config.LLM_ROUTER_ROUTES_PATH;
const text = await readFile(path, "utf8");
const arr = JSON.parse(text) as Route[];
if (!Array.isArray(arr)) {
throw new Error("Routes config must be a flat array of routes");
}
const seen = new Set<string>();
for (const r of arr) {
if (!r?.name || !r?.description || !r?.primary_model) {
throw new Error(`Invalid route entry: ${JSON.stringify(r)}`);
}
if (seen.has(r.name)) {
throw new Error(`Duplicate route name: ${r.name}`);
}
seen.add(r.name);
}
ROUTES = arr;
loaded = true;
return ROUTES;
}
export async function getRoutes(): Promise<Route[]> {
if (!loaded) await loadPolicy();
return ROUTES;
}
export function resolveRouteModels(
routeName: string,
routes: Route[],
fallbackModel: string
): { candidates: string[] } {View on GitHub (pinned to 6b01dc5a68)
Solutions
- Search the routes JSON for the duplicated name printed in the error.
- Rename one of the entries so every name is unique (names are the lookup key for resolveRouteModels).
- If the duplication is intentional (e.g. A/B variants), encode the variant in the name ("chat_v2") instead.
- Re-run loadPolicy(); dedup is re-evaluated from scratch on each load.
Example fix
// before
[
{ "name": "chat", "description": "casual", "primary_model": "gpt-4o" },
{ "name": "chat", "description": "reasoning", "primary_model": "o1" }
]
// after
[
{ "name": "chat", "description": "casual", "primary_model": "gpt-4o" },
{ "name": "chat_reasoning", "description": "reasoning", "primary_model": "o1" }
] Defensive patterns
Strategy: validation
Validate before calling
const arr = JSON.parse(text) as Route[];
const names = arr.map((r) => r.name);
const dupes = names.filter((n, i) => names.indexOf(n) !== i);
if (dupes.length) {
throw new Error(`Duplicate route names in config: ${[...new Set(dupes)].join(", ")}`);
} Type guard
function hasUniqueNames(routes: { name: string }[]): boolean {
return new Set(routes.map((r) => r.name)).size === routes.length;
} Try / catch
try {
await loadPolicy();
} catch (e) {
if (String(e?.message ?? "").startsWith("Duplicate route name")) {
// surface the conflicting names to the operator, then exit
console.error(e.message);
process.exit(1);
}
throw e;
} Prevention
- Lint the routes file for duplicate name keys in a pre-commit hook.
- Use a single source of truth (e.g. code-generated from a typed list) so duplicates surface as a compile error.
- Run the dedupe check in a schema validator (uniqueItems on a name-only projection).
When it happens
Trigger: The routes JSON contains two objects whose name field is byte-for-byte identical (case-sensitive). The second occurrence triggers the throw on seen.has(r.name).
Common situations: Copying a route entry as a template and forgetting to rename it; merging two route files that both define a "casual_conversation" route; case differences ("Chat" vs "chat") are NOT duplicates but visually confusing ones.
Related errors
- Invalid route entry: ${JSON.stringify(r)}
- Router multimodal is enabled but LLM_ROUTER_MULTIMODAL_MODEL
- Routes config must be a flat array of routes
- mode must be legacy, observe, or enforce
- Key exceeds maximum nesting depth of ${MAX_NESTING_DEPTH}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/743ce8f2afa3cc34.
Report an issue: GitHub.