ruvnet/ruflo · error
Dual-mode config must export a workers array: ${absolute}
Error message
Dual-mode config must export a workers array: ${absolute} What it means
loadWorkerConfig() reads a worker config from a .json file (JSON.parse) or imports a .js/.mjs/.ts module (dynamic import), unwraps a default export if present, and requires the resulting object to have a `workers` array. Any config whose effective export lacks an array-typed workers key — missing key, typoed key, wrong type, or a module exporting a function/class instead of a plain object — is rejected with the absolute path of the offending file.
Source
Thrown at v3/@claude-flow/codex/src/dual-mode/cli.ts:195
console.log();
printResults(result);
});
}
export async function loadWorkerConfig(
configPath: string,
cwd = process.cwd(),
): Promise<{ workers: WorkerConfig[]; taskContext?: string }> {
const absolute = path.resolve(cwd, configPath);
const loaded = path.extname(absolute).toLowerCase() === '.json'
? JSON.parse(await readFile(absolute, 'utf8'))
: await import(pathToFileURL(absolute).href);
const config = loaded.default && typeof loaded.default === 'object'
? loaded.default
: loaded;
if (!Array.isArray(config.workers)) {
throw new Error(`Dual-mode config must export a workers array: ${absolute}`);
}
return {
workers: config.workers,
...(typeof config.taskContext === 'string' ? { taskContext: config.taskContext } : {}),
};
}
/**
* List available templates
*/
function createTemplateCommand(): Command {
return new Command('templates')
.description('List available collaboration templates')
.action(() => {
console.log(chalk.bold('\nAvailable Collaboration Templates:\n'));
console.log(chalk.cyan('feature') + ' - Feature Development Swarm');
console.log(' Pipeline: architect → coder → tester → reviewer');View on GitHub (pinned to fa13ee4ad6)
Solutions
- Make the file export (default or namespace) an object containing a workers array: `export default { workers: [{ id: 'impl', platform: 'claude', role: 'implementer', prompt: 'Implement X' }] }`.
- Compare against the absolute path in the error message to be sure you edited the file that is actually loaded (path.resolve(cwd, configPath)).
- For JSON configs, verify with a quick check that `Array.isArray(JSON.parse(text).workers)` is true.
- Optionally include `taskContext` as a string — it is the only other accepted key.
Example fix
// before — workers.config.ts
export default { worker: [ { id: 'impl', platform: 'claude', role: 'impl', prompt: 'Do X' } ] };
// → Error: Dual-mode config must export a workers array: /repo/workers.config.ts
// after
export default {
workers: [ { id: 'impl', platform: 'claude', role: 'implementer', prompt: 'Do X' } ],
taskContext: 'Refactor the auth module',
}; Defensive patterns
Strategy: type-guard
Validate before calling
const loaded = await import(pathToFileURL(absolute).href);
const candidate = loaded.default && typeof loaded.default === 'object' ? loaded.default : loaded;
if (!isWorkerConfigArray((candidate as { workers?: unknown }).workers)) {
throw new Error(`${absolute} must export { workers: WorkerConfig[] }`);
} Type guard
function isWorkerConfigArray(v: unknown): v is Array<{ id: string; platform: string; role: string; prompt: string }> {
return Array.isArray(v) && v.length > 0 && v.every(w =>
typeof w === 'object' && w !== null &&
typeof (w as { id?: unknown }).id === 'string' &&
((w as { platform?: unknown }).platform === 'claude' || (w as { platform?: unknown }).platform === 'codex') &&
typeof (w as { prompt?: unknown }).prompt === 'string');
} Try / catch
try {
const { workers, taskContext } = await loadWorkerConfig(configPath, cwd);
} catch (err) {
if (err instanceof Error && err.message.startsWith('Dual-mode config must export a workers array:')) {
throw new Error(`config shape wrong — the file at the printed path needs export default { workers: [...] }`);
}
throw err; // import/JSON.parse failures surface as different errors
} Prevention
- Standardize on `export default { workers: [...] }` for .ts/.js configs — it survives the default-unwrap logic in both ESM and CJS interop
- The error prints the resolved absolute path — always diff it against the file you edited before debugging further
- Validate the shape with the type guard in CI when configs are generated
- Prefer JSON configs for machine-generated worker lists; they fail loudly and early
When it happens
Trigger: (1) A config module that exports only `taskContext` or metadata and forgets workers; (2) key spelled `worker:` instead of `workers:`; (3) `export default () => ({ workers })` — the default is a function, not an object; (4) a .json file whose top level is an array or where workers is an object rather than an array; (5) editing the wrong file — the error prints the resolved absolute path to confirm.
Common situations: First-time dual-mode configs copied from partial examples; ESM/CJS interop where module.exports vs export default produce different shapes; refactors that renamed the key; JSON hand-edits introducing type changes.
Related errors
- localCompute: no adapter for graphId=${input.graphId}
- RuvllmConfig.modelsDir is required
- Key exceeds maximum nesting depth of ${MAX_NESTING_DEPTH}
- Dangerous key segment rejected: ${part}
- task step ${step.stepId} requires config.agentId or workflow
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/876502910b11e31c.
Report an issue: GitHub.