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

  1. 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' }] }`.
  2. Compare against the absolute path in the error message to be sure you edited the file that is actually loaded (path.resolve(cwd, configPath)).
  3. For JSON configs, verify with a quick check that `Array.isArray(JSON.parse(text).workers)` is true.
  4. 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

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


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