{"record":{"id":"d25fe2e17e9b3fec","repo":"ruvnet/ruflo","slug":"case-fold-repository-path-collision-prior-and","errorCode":null,"errorMessage":"case-fold repository path collision: ${prior} and ${path}","messagePattern":"case-fold repository path collision: (.+?) and (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/codex/src/harness/repository-state.ts","lineNumber":221,"sourceCode":"    || isAbsolute(normalized)\n    || normalized.startsWith('-')\n    || normalized.split('/').some((part) => part === '' || part === '.' || part === '..')\n  ) {\n    throw new Error(`unsafe repository-relative path: ${path}`);\n  }\n  return normalized;\n}\n\nfunction assertNoPathCollisions(paths: readonly string[]): void {\n  const exact = new Set<string>();\n  const folded = new Map<string, string>();\n  for (const path of paths) {\n    if (exact.has(path)) throw new Error(`duplicate repository path: ${path}`);\n    exact.add(path);\n    const key = portableCaseFold(path);\n    const prior = folded.get(key);\n    if (prior !== undefined && prior !== path) {\n      throw new Error(`case-fold repository path collision: ${prior} and ${path}`);\n    }\n    folded.set(key, path);\n  }\n}\n\nfunction repositoryPaths(repoRoot: string, includeUntracked: boolean): readonly string[] {\n  const tracked = splitNul(gitBuffer(repoRoot, ['ls-files', '-z'])).map(decodeGitPath);\n  const untracked = includeUntracked\n    ? splitNul(gitBuffer(repoRoot, ['ls-files', '--others', '--exclude-standard', '-z'])).map(decodeGitPath)\n    : [];\n  const paths = [...tracked, ...untracked].sort(codeUnitCompare);\n  assertNoPathCollisions(paths);\n  return paths;\n}\n\nfunction assertInsideRepository(repoRoot: string, candidate: string): void {\n  const rel = relative(repoRoot, candidate);\n  if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/codex/src/harness/repository-state.ts#L203-L239","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Find the pair: `git ls-files | awk '{print tolower($0)}' | sort | uniq -d`","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","If one file is junk, delete it and commit the rename"],"exampleFix":"# two-step case rename on macOS/Windows\ngit mv Readme.md Readme.md.tmp\ngit mv Readme.md.tmp README.md\ngit commit -m \"fix case-fold collision\"","handlingStrategy":"validation","validationCode":"const fold = (s: string) => s.toLowerCase();\nconst seen = new Set<string>();\nfor (const p of paths) {\n  const k = fold(p);\n  if (seen.has(k)) throw new Error(`case collision: ${p}`);\n  seen.add(k);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Add a CI lint that runs `git ls-files | awk '{print tolower($0)}' | sort | uniq -d` and fails on output","Do case renames in two git-mv steps on case-insensitive platforms","Review merges that touch files whose names differ only by case"],"tags":["filesystem","case-sensitivity","git","portability"],"backgroundTag":"case-insensitive-filename-collision","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}