ruvnet/ruflo · error · Error

case-fold repository path collision: ${prior} and ${path}

Error message

case-fold repository path collision: ${prior} and ${path}

What it means

assertNoPathCollisions() also folds every path case-insensitively (portableCaseFold) and rejects two distinct paths that are equal after folding, e.g. 'README.md' and 'Readme.md'. The harness needs unambiguous path identity because the capture is content-addressed and may be checked out on case-insensitive filesystems where the two names cannot coexist.

Source

Thrown at v3/@claude-flow/codex/src/harness/repository-state.ts:221

    || isAbsolute(normalized)
    || normalized.startsWith('-')
    || normalized.split('/').some((part) => part === '' || part === '.' || part === '..')
  ) {
    throw new Error(`unsafe repository-relative path: ${path}`);
  }
  return normalized;
}

function assertNoPathCollisions(paths: readonly string[]): void {
  const exact = new Set<string>();
  const folded = new Map<string, string>();
  for (const path of paths) {
    if (exact.has(path)) throw new Error(`duplicate repository path: ${path}`);
    exact.add(path);
    const key = portableCaseFold(path);
    const prior = folded.get(key);
    if (prior !== undefined && prior !== path) {
      throw new Error(`case-fold repository path collision: ${prior} and ${path}`);
    }
    folded.set(key, path);
  }
}

function repositoryPaths(repoRoot: string, includeUntracked: boolean): readonly string[] {
  const tracked = splitNul(gitBuffer(repoRoot, ['ls-files', '-z'])).map(decodeGitPath);
  const untracked = includeUntracked
    ? splitNul(gitBuffer(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z'])).map(decodeGitPath)
    : [];
  const paths = [...tracked, ...untracked].sort(codeUnitCompare);
  assertNoPathCollisions(paths);
  return paths;
}

function assertInsideRepository(repoRoot: string, candidate: string): void {
  const rel = relative(repoRoot, candidate);
  if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Find the pair: `git ls-files | awk '{print tolower($0)}' | sort | uniq -d`
  2. Rename one side with a two-step `git mv` (git mv -f to a temp name, then to the cased name you want) since one-shot case renames are no-ops on case-insensitive filesystems
  3. If one file is junk, delete it and commit the rename

Example fix

# two-step case rename on macOS/Windows
git mv Readme.md Readme.md.tmp
git mv Readme.md.tmp README.md
git commit -m "fix case-fold collision"
Defensive patterns

Strategy: validation

Validate before calling

const fold = (s: string) => s.toLowerCase();
const seen = new Set<string>();
for (const p of paths) {
  const k = fold(p);
  if (seen.has(k)) throw new Error(`case collision: ${p}`);
  seen.add(k);
}

Prevention

When it happens

Trigger: The worktree contains two files (or a file and a directory name component) differing only by letter case — typically after extracting an archive, merging branches from case-insensitive machines, or copying trees on macOS/Windows.

Common situations: A merge brings in 'Makefile' while the repo has 'makefile'; archives extracted on macOS/Windows create case-variant duplicates; Linux CI then fails here while the developer's machine appears fine.

Related errors


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