ruvnet/ruflo · error · Error

repoRoot must be the git top-level: ${top}

Error message

repoRoot must be the git top-level: ${top}

What it means

The CodexWorktreeCoordinator constructor resolves the passed repoRoot and requires it to equal (after resolve()) the `git rev-parse --show-toplevel` of that directory. Passing a subdirectory of the repository (or a path outside one) throws, because worktrees and the in-repo registry (.claude-flow/swarm/worktrees) are anchored to the top-level.

Source

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

    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
    maxBuffer: 4 * 1024 * 1024,
  }).trim();
}

function assertId(value: string, label: string): void {
  if (!SAFE_ID.test(value)) throw new Error(`invalid ${label}: ${value}`);
}

export class CodexWorktreeCoordinator {
  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);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Construct with the git top-level: execSync('git rev-parse --show-toplevel').trim()
  2. Add a resolveRoot helper that walks up from cwd until .git is found, then pass that
  3. Run the coordinator's entrypoint from the repository root

Example fix

// before
new CodexWorktreeCoordinator('v3/@claude-flow/codex'); // subdir -> throws
// after
import { execSync } from 'node:child_process';
const root = execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
new CodexWorktreeCoordinator(root);
Defensive patterns

Strategy: validation

Validate before calling

import { execSync } from 'node:child_process';
const gitRoot = (p: string) =>
  realpathSync(execSync('git rev-parse --show-toplevel', { cwd: p, encoding: 'utf8' }).trim());
if (gitRoot(p) !== realpathSync(resolve(p))) throw new Error('not the git top-level');

Prevention

When it happens

Trigger: new CodexWorktreeCoordinator(process.cwd()) while the process runs in a package subdirectory; constructing with a monorepo package path; passing a directory that is not a git repository at all (rev-parse then fails or returns a different root).

Common situations: Monorepos where CLIs start in v3/@claude-flow/codex instead of the repo root; scripts hardcoding relative package paths; running inside a linked worktree's subdirectory.

Related errors


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