ruvnet/ruflo · error · Error

Git path is not NFC-normalized: ${path}

Error message

Git path is not NFC-normalized: ${path}

What it means

Even with valid UTF-8, decodeGitPath requires every tracked path to be in Unicode Normalization Form C (precomposed): path must equal path.normalize('NFC'). macOS filesystems return decomposed (NFD) names for accented characters, so 'café' stored with a combining accent fails the comparison; the offending path is included in the message. Normalization form is part of path identity because paths feed content digests.

Source

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

  const records: Buffer[] = [];
  let start = 0;
  for (let index = 0; index < output.length; index += 1) {
    if (output[index] !== 0) continue;
    if (index > start) records.push(output.subarray(start, index));
    start = index + 1;
  }
  if (start !== output.length) throw new Error('Git returned a non-NUL-terminated path list');
  return records;
}

function decodeGitPath(bytes: Buffer): string {
  const path = bytes.toString('utf8');
  if (!Buffer.from(path, 'utf8').equals(bytes)) {
    throw new Error('Git path is not valid round-trip UTF-8');
  }
  assertUnicodeScalarString(path);
  if (path !== path.normalize('NFC')) {
    throw new Error(`Git path is not NFC-normalized: ${path}`);
  }
  return normalizeRelativePath(path);
}

function normalizeRelativePath(path: string): string {
  assertUnicodeScalarString(path);
  if (path.includes('\\')) throw new Error(`ambiguous repository path separator: ${path}`);
  const normalized = path.normalize('NFC');
  if (
    normalized.length === 0
    || isAbsolute(normalized)
    || normalized.startsWith('-')
    || normalized.split('/').some((part) => part === '' || part === '.' || part === '..')
  ) {
    throw new Error(`unsafe repository-relative path: ${path}`);
  }
  return normalized;
}

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Rename offenders to NFC: `convmv -r -f nfd -t nfc --notest .` after reviewing the dry run
  2. Or `git mv` the file to a freshly typed precomposed name
  3. Normalize user-supplied filenames to NFC at ingestion (name.normalize('NFC')) before writing into the repo
  4. Add a CI check that rejects NFD paths in git ls-files output

Example fix

// before: filename received from macOS upload, stored decomposed
writeFileSync(`uploads/${userFileName}`, data); // userFileName is NFD

// after: normalize before it touches the repo
writeFileSync(join('uploads', userFileName.normalize('NFC')), data);
Defensive patterns

Strategy: try-catch

Validate before calling

function findNonNfcPaths(paths: readonly string[]): string[] {
  return paths.filter((p) => p !== p.normalize('NFC'));
}

Type guard

function isNfcPath(path: string): boolean {
  return path === path.normalize('NFC');
}

Try / catch

try {
  const state = captureSourceState(repoRoot);
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Git path is not NFC-normalized')) {
    const path = error.message.slice('Git path is not NFC-normalized: '.length);
    throw new Error(`rename to NFC (git mv or convmv -f nfd -t nfc): ${path}`);
  }
  throw error;
}

Prevention

When it happens

Trigger: Creating or checking out files with diacritics on macOS (NFD) and then running the harness's source-state snapshot; names synced from iOS clients; NFD names committed by a contributor whose editor or FS decomposes accents.

Common situations: Cross-platform teams (macOS NFD vs Linux NFC); cloud-sync folders (iCloud/Dropbox) that decompose names; filenames typed with dead-key input methods that emit combining sequences.

Related errors


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