presubmit is green before `git add` and red after: every `git ls-files` scan cannot see the file you are about to commit nf-pgi ← Beads

open priority 2 task unassigned

Twinned from https://github.com/cfsh/ninefold/issues/754 by tools/beads/import_github.py's reconcile pass.

presubmit --all green before git add is not the same answer as green after, and the difference has already taken a branch red.

Found on #748 (design 005 piece 2), filing unclaimed.

What happened

I added a new test file, ran ./presubmit.py --all, got PASS on all three lanes, committed, and pushed. The branch was red on the remote from the first commit, and I did not find out until the next reviewer pass.

The cause is not a flake. shared/tests/UserPathGuardTests.cs scans tracked files:

```csharp var psi = new ProcessStartInfo("git", "ls-files -z -- " + ...) ```

so an untracked file is invisible to it. My new file tripped *both* of its scans — a user:// literal in an assertion message, and a UserPaths.Redirect = literal in a search string — the instant git add made it tracked, and not one moment earlier.

Why the scan is right and this is still a trap

The git ls-files choice is deliberate and well-argued in that file (#372): a directory walk treats .claude/worktrees/ and untracked scratch copies as authoritative source, which produced spurious failures pointing at a pre-#358 copy of VisualProbe.cs. Tracked files are the only thing that guard has any business policing. I would not change it.

The trap is the interaction with the workflow every session follows — write, run the gate, commit — where the gate answers a different question before and after the middle step.

Blast radius

Any presubmit check that reads git ls-files inherits it. Today that is at least:

The shellcheck one is the sharpest: a brand-new script passes the gate that exists to lint it.

Directions, not a decision

⚠️ Worth noting the same shape bit me a second time in the same chunk, in a test I wrote myself — a reader-hunt over git ls-files that would not have seen a newly added module either. It is not a one-off property of that one guard.

Unclaimed.

Dependencies

None.

Comments

No comments.

Add a comment