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

  1. Search the routes JSON for the duplicated name printed in the error.
  2. Rename one of the entries so every name is unique (names are the lookup key for resolveRouteModels).
  3. If the duplication is intentional (e.g. A/B variants), encode the variant in the name ("chat_v2") instead.
  4. 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

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


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/743ce8f2afa3cc34. Report an issue: GitHub.