ruvnet/ruflo · error · Error

at least one agent is required

Error message

at least one agent is required

What it means

CodexWorktreeCoordinator.prepare() provisions one worktree per agent; with an empty agents array there is nothing to provision and no run to record, so it fails fast with 'at least one agent is required' before touching git or the registry. Each agent id is additionally validated for uniqueness and SAFE_ID compliance.

Source

Thrown at v3/@claude-flow/codex/src/worktrees/coordinator.ts:53

  readonly repoRoot: string;
  readonly registryDir: string;
  readonly worktreeBase: string;

  constructor(repoRoot: string) {
    this.repoRoot = resolve(repoRoot);
    const top = git(this.repoRoot, ['rev-parse', '--show-toplevel']);
    if (resolve(top) !== this.repoRoot) throw new Error(`repoRoot must be the git top-level: ${top}`);
    this.registryDir = join(this.repoRoot, '.claude-flow', 'swarm', 'worktrees');
    this.worktreeBase = join(dirname(this.repoRoot), '.ruflo-worktrees', basename(this.repoRoot));
  }

  prepare(
    runId: string,
    agents: Array<{ id: string; readOnly?: boolean }>,
    options: { allowDirty?: boolean; baseRef?: string } = {},
  ): WorktreeRunRecord {
    assertId(runId, 'run id');
    if (!agents.length) throw new Error('at least one agent is required');
    const unique = new Set<string>();
    for (const agent of agents) {
      assertId(agent.id, 'agent id');
      if (unique.has(agent.id)) throw new Error(`duplicate agent id: ${agent.id}`);
      unique.add(agent.id);
    }
    const registryPath = this.registryPath(runId);
    if (existsSync(registryPath)) return this.status(runId);
    if (!options.allowDirty && git(this.repoRoot, ['status', '--porcelain']).length > 0) {
      throw new Error('refusing to prepare writing worktrees from a dirty repository');
    }

    mkdirSync(this.registryDir, { recursive: true });
    mkdirSync(join(this.worktreeBase, runId), { recursive: true });
    const assignments: WorktreeAssignment[] = [];
    try {
      for (const agent of agents) {
        const readOnly = agent.readOnly === true;

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Check agents.length before calling prepare and surface a meaningful upstream error
  2. Fix the preceding step that should have produced agents (swarm spawn, config read, filter logic)
  3. Log the agent list right before the call so silent-empty cases are visible

Example fix

// before
const agents = spawned.filter(a => a.role === 'coder'); // may be []
coordinator.prepare(runId, agents); // throws
// after
if (agents.length === 0) throw new Error('spawn produced no coder agents');
coordinator.prepare(runId, agents);
Defensive patterns

Strategy: validation

Validate before calling

if (!Array.isArray(agents) || agents.length === 0) throw new Error('no agents to provision');

Type guard

function isNonEmptyAgentList(a: unknown): a is Array<{ id: string; readOnly?: boolean }> {
  return Array.isArray(a) && a.length > 0 && a.every(x => typeof x?.id === 'string');
}

Prevention

When it happens

Trigger: Calling prepare(runId, []) — typically because the callers agent list came back empty (a swarm spawn produced zero agents, a filter removed all entries, or a config mapped to an empty array).

Common situations: Programmatic orchestrators forward an agents array that a preceding spawn step populated; when spawn fails silently or filters over-prune (e.g. readOnly-only filtering), the array is empty and prepare is still called.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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