ruvnet/ruflo · error · Error

ambiguous repository path separator: ${path}

Error message

ambiguous repository path separator: ${path}

What it means

normalizeRelativePath rejects any repository-relative path containing a backslash. On POSIX, a backslash can be a literal filename character; on Windows, it is a directory separator — so such paths are ambiguous cross-platform and are refused before entering the source-state digest. The offending path is embedded in the message.

Source

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

  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;
}

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);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. git mv the offending file to a name with the backslash replaced (hyphen, underscore, or restructured with '/': 'dir/file.txt')
  2. Fix the generating tool to use path.posix.join for repository-relative names
  3. Add a CI scan of git ls-files output for the backslash character to catch regressions

Example fix

// before: Windows-style join leaks into a tracked filename
const target = `reports\\${name}.txt`; // literal backslash on POSIX

// after: POSIX separators for repository-relative paths
const target = path.posix.join('reports', `${name}.txt`);
Defensive patterns

Strategy: try-catch

Validate before calling

function findBackslashPaths(paths: readonly string[]): string[] {
  return paths.filter((p) => p.includes('\\'));
}

Type guard

function isPosixRelativePath(path: string): boolean {
  return !path.includes('\\') && !path.startsWith('-') && !path.split('/').some((part) => part === '' || part === '.' || part === '..');
}

Try / catch

try {
  const state = captureSourceState(repoRoot);
} catch (error) {
  if (error instanceof Error && error.message.startsWith('ambiguous repository path separator')) {
    const path = error.message.slice('ambiguous repository path separator: '.length);
    throw new Error(`rename file containing a backslash (git mv): ${JSON.stringify(path)}`);
  }
  throw error;
}

Prevention

When it happens

Trigger: A tracked file literally named with a backslash (e.g. a name created via shell redirection or a Windows-origin script writing 'dir\file.txt' as a single filename); tools emitting Windows-style relative paths; archives extracted with translated separators producing backslash names.

Common situations: CI steps or generators from Windows environments using backslash joins; files created by `echo x > 'a\b'` on Linux; vendored archives whose entries use backslashes.

Related errors


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