ruvnet/ruflo · error

Routes config must be a flat array of routes

Error message

Routes config must be a flat array of routes

What it means

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.

Solutions

  1. Open the file at $LLM_ROUTER_ROUTES_PATH and make the root a flat array: [{ name, description, primary_model, ... }, ...].
  2. If you keep an envelope in source, unwrap it when writing the file or point LLM_ROUTER_ROUTES_PATH at a generated flat file.
  3. Validate with: node -e "console.log(Array.isArray(require('./routes.json')))" or jq 'type' routes.json (should print 'array').
  4. Restart the server so loadPolicy() re-reads the corrected file.

Example fix

// routes.json (before)
{ "routes": [ { "name": "code", "description": "...", "primary_model": "..." } ] }

// routes.json (after)
[ { "name": "code", "description": "...", "primary_model": "..." } ]
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from "node:fs";

const raw = JSON.parse(readFileSync(process.env.LLM_ROUTER_ROUTES_PATH!, "utf8"));
if (!Array.isArray(raw)) {
	throw new Error("routes file root must be an array");
}

Type guard

function isRouteArray(v: unknown): v is unknown[] {
	return Array.isArray(v);
}

Prevention

When it happens

Trigger: 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).

Common situations: 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).

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/b647b3e2c2241a1f. Report an issue: GitHub.

Appendix: source

Thrown at ruflo/src/ruvocal/src/lib/server/router/policy.ts:13

import { readFile } from "node:fs/promises";
import { config } from "$lib/server/config";
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();

View on GitHub (pinned to fa13ee4ad6)