{"record":{"id":"b647b3e2c2241a1f","repo":"ruvnet/ruflo","slug":"routes-config-must-be-a-flat-array-of-routes","errorCode":null,"errorMessage":"Routes config must be a flat array of routes","messagePattern":"Routes config must be a flat array of routes","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/router/policy.ts","lineNumber":13,"sourceCode":"import { readFile } from \"node:fs/promises\";\nimport { config } from \"$lib/server/config\";\nimport 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();","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/ruflo/src/ruvocal/src/lib/server/router/policy.ts#L1-L31","documentation":"Thrown by loadPolicy() in the router policy loader (policy.ts). The routes file pointed to by LLM_ROUTER_ROUTES_PATH must be a top-level JSON array of route objects; after JSON.parse succeeds, a non-array top level (object, string, number) means the file format is wrong and the router cannot load its routing table.","triggerScenarios":"LLM_ROUTER_ROUTES_PATH points at a JSON file whose root is an object — commonly { \"routes\": [...] } — or at the wrong file entirely (a config object, a single route, or output from another tool).","commonSituations":"Author wrapping the array for readability; examples/docs showing an envelope object; editing the wrong path (LLM_ROUTER_ROUTES_PATH still pointing at a stale file).","solutions":["Open the file at $LLM_ROUTER_ROUTES_PATH and make the root a flat array: [{ name, description, primary_model, ... }, ...].","If you keep an envelope in source, unwrap it when writing the file or point LLM_ROUTER_ROUTES_PATH at a generated flat file.","Validate with: node -e \"console.log(Array.isArray(require('./routes.json')))\" or jq 'type' routes.json (should print 'array').","Restart the server so loadPolicy() re-reads the corrected file."],"exampleFix":"// routes.json (before)\n{ \"routes\": [ { \"name\": \"code\", \"description\": \"...\", \"primary_model\": \"...\" } ] }\n\n// routes.json (after)\n[ { \"name\": \"code\", \"description\": \"...\", \"primary_model\": \"...\" } ]","handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\n\nconst raw = JSON.parse(readFileSync(process.env.LLM_ROUTER_ROUTES_PATH!, \"utf8\"));\nif (!Array.isArray(raw)) {\n\tthrow new Error(\"routes file root must be an array\");\n}","typeGuard":"function isRouteArray(v: unknown): v is unknown[] {\n\treturn Array.isArray(v);\n}","tryCatchPattern":null,"preventionTips":["Validate the routes file in CI with a JSON-schema or unit test before deploy.","Keep a canonical example routes file and diff against it on change.","Prefer generating the file from typed code so its shape cannot drift."],"tags":["router","json","configuration","validation"],"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"}