ruvnet/ruflo · error · Error

Git status reported changes that exact-state capture could n

Error message

Git status reported changes that exact-state capture could not identify

What it means

The porcelain status was non-empty, yet the exact-state capture found nothing dirty: no tracked patch bytes, no untracked manifest entries, no dirty submodules. Rather than emit a sourceStateId for an unattributable state, the harness throws. It is a consistency guarantee between `git status` and the content-level capture.

Source

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

      baseCommit,
      treeId,
      sourceStateId: digest(canonicalJson(identity)),
    };
  }

  repositoryPaths(repoRoot, true);
  const first = captureContentState(repoRoot);
  const second = captureContentState(repoRoot);
  if (canonicalJson(first) !== canonicalJson(second)) {
    throw new Error('repository changed while capturing exact source state');
  }

  const repository = repositoryIdentity(repoRoot);
  const dirty = second.trackedPatch.bytes > 0
    || second.untrackedManifest.entries.length > 0
    || second.submodules.some(hasDirtySubmodule);

  if (!dirty) throw new Error('Git status reported changes that exact-state capture could not identify');

  const identity = {
    version: 1,
    scope: 'git-visible',
    repositoryId: repository.repositoryId,
    baseCommit: second.baseCommit,
    tree: second.treeId,
    trackedPatch: second.trackedPatch,
    untrackedManifest: second.untrackedManifest,
    submodules: second.submodules,
  };
  return {
    version: 1,
    scope: 'git-visible',
    kind: 'dirty',
    clean: false,
    repository,
    baseCommit: second.baseCommit,

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Run `git update-index --refresh` (or a no-op `git status` then `git diff`) to settle stat info, then retry
  2. Check submodule noise: `git submodule status` and diff `git config -f .gitmodules`
  3. If a submodule is only config-dirty, set `submodule.<name>.ignore = dirty` or commit the submodule pointer change

Example fix

# settle racy stat info, then re-capture
git update-index --refresh || true
git status --porcelain  # should now match actual content
# re-run the capture command
Defensive patterns

Strategy: validation

Validate before calling

import { execSync } from 'node:child_process';
execSync('git update-index -q --refresh', { cwd: root });
const status = execSync('git status --porcelain', { cwd: root, encoding: 'utf8' });
const diff = execSync('git diff --binary --full-index HEAD', { cwd: root, encoding: 'buffer' });
const attributable = diff.length > 0 || /\?\?/.test(status);

Try / catch

try { return capture(root); }
catch (e) {
  if (/could not identify/.test(String(e))) { execSync('git update-index --refresh', { cwd: root }); return capture(root); }
  throw e;
}

Prevention

When it happens

Trigger: Stat-dirty files: mtime/inode changes with identical content so `git status` reports modified but `git diff --binary` is empty; submodule status noise (modified config vs content); racy-git after checkouts or touches; files ignored by diff drivers.

Common situations: Running `touch src/**` or a checkout that rewrites mtimes right before capture; submodules whose status differs only by config (ignore=dirty settings); CI caching that restores mtimes incorrectly.

Related errors


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