{"record":{"id":"743ce8f2afa3cc34","repo":"ruvnet/ruflo","slug":"duplicate-route-name-r-name","errorCode":null,"errorMessage":"Duplicate route name: ${r.name}","messagePattern":"Duplicate route name: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/router/policy.ts","lineNumber":21,"sourceCode":"import type { Route } from \"./types\";\n\nlet ROUTES: Route[] = [];\nlet loaded = false;\n\nexport async function loadPolicy(): Promise<Route[]> {\n\tconst path = config.LLM_ROUTER_ROUTES_PATH;\n\tconst text = await readFile(path, \"utf8\");\n\tconst arr = JSON.parse(text) as Route[];\n\tif (!Array.isArray(arr)) {\n\t\tthrow new Error(\"Routes config must be a flat array of routes\");\n\t}\n\tconst seen = new Set<string>();\n\tfor (const r of arr) {\n\t\tif (!r?.name || !r?.description || !r?.primary_model) {\n\t\t\tthrow new Error(`Invalid route entry: ${JSON.stringify(r)}`);\n\t\t}\n\t\tif (seen.has(r.name)) {\n\t\t\tthrow new Error(`Duplicate route name: ${r.name}`);\n\t\t}\n\t\tseen.add(r.name);\n\t}\n\tROUTES = arr;\n\tloaded = true;\n\treturn ROUTES;\n}\n\nexport async function getRoutes(): Promise<Route[]> {\n\tif (!loaded) await loadPolicy();\n\treturn ROUTES;\n}\n\nexport function resolveRouteModels(\n\trouteName: string,\n\troutes: Route[],\n\tfallbackModel: string\n): { candidates: string[] } {","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/server/router/policy.ts#L3-L39","documentation":"Thrown by loadPolicy() when two route entries in the routes file share the same name. Route names are the lookup key for routing decisions and telemetry, so duplicates are rejected during validation rather than silently overwritten — the message names the conflicting route.","triggerScenarios":"The JSON array at LLM_ROUTER_ROUTES_PATH contains two objects with an identical non-empty name (e.g., copy-pasting a route and editing only the model).","commonSituations":"Duplicating an existing route to A/B test a different primary_model and forgetting to rename; merging two hand-maintained route files that both define a 'default' route.","solutions":["Rename one of the duplicated routes so every name is unique (grep the file for the name from the error).","Check for duplicates mechanically: jq 'group_by(.name) | map(select(length > 1)) | map(.[0].name)' routes.json.","If the duplicate was intentional to express priority, instead use the ordering or scoring fields of a single route.","Reload/restart so loadPolicy() revalidates the file."],"exampleFix":"// before\n[ { \"name\": \"code\", ... }, { \"name\": \"code\", ... } ]\n\n// after\n[ { \"name\": \"code\", ... }, { \"name\": \"code-heavy\", ... } ]","handlingStrategy":"validation","validationCode":"const names = routes.map((r) => r.name);\nconst dupes = names.filter((n, i) => names.indexOf(n) !== i);\nif (dupes.length > 0) throw new Error(`duplicate route names: ${dupes.join(\", \")}`);","typeGuard":"function hasUniqueNames(routes: { name: string }[]): boolean {\n\treturn new Set(routes.map((r) => r.name)).size === routes.length;\n}","tryCatchPattern":null,"preventionTips":["Lint the routes file for duplicate names in CI (jq group_by check).","Generate route names programmatically (slugify) to avoid copy-paste collisions.","Treat route names as stable identifiers: never reuse one for a different purpose."],"tags":["router","json","validation","duplicates"],"backgroundTag":"invalid-config-file","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}