fix(coord): keep a zero-row overlap walk zero rows across the cache - #448
Merged
Conversation
wshallwshall
enabled auto-merge (squash)
August 19, 2026 12:48
Found while auditing scripts/ for other instances of the unroll defect fixed in the previous commit. Same mechanism, different script, and it survives a round trip to disk where the other one did not. Build-Map returns an empty array, PowerShell unrolls it to AutomationNull, and in process that is harmless: assignment preserves it and the zero-rows guard still fires. It does not survive serialisation. ConvertTo-Json writes "rows": null, the next run reads it back with @($c.rows), and that is a one-element array holding $null. Count is 1, the guard does not fire, and the human report prints a phantom occupant: a blank worktree on a blank branch with 0 changed files. The hot path arms this constantly rather than rarely. collision_gate.ps1 runs overlap.ps1 with -Json on every gated edit, and the cache is written before the -File early exit, so any bare overlap.ps1 inside the cache window inherits it. An invented collision is not a safe direction to fail in. It is the answer people work around, and a report that cries wolf on a quiet repo is one they stop reading, which is how a real collision goes unnoticed. Kept out of the install-gate commit deliberately: different file, different failure mode, and it needed its own test rather than riding in on one earned elsewhere. The test drives real throwaway git repos, because the whole question is what the script writes to disk and reads back; stub rows would assert only that a value someone else computed gets carried, and the value is exactly what was wrong.
wshallwshall
force-pushed
the
claude/overlap-zero-row-cache
branch
from
August 19, 2026 12:58
19942a5 to
14defcb
Compare
wshallwshall
added a commit
that referenced
this pull request
Aug 19, 2026
…nd a latent NULL unroll (#449) * backlog: file #1290 -- the connscale CPU-probe walk times out on hosted Windows runners A required status check is intermittently red on main. CI run 32255231838 at de896e0 failed `test (windows-2022, py3.14)` on test_subtree_re_resolution_picks_up_a_late_spawned_child: the process-table walk returned None on all 6 attempts in 30s. The identical leg passed on PR #448 in the same hour on the same runner image, and neither branch touches the probe, so the nondeterminism is measured rather than assumed. The assertion that fires is the one the test's author separated out as an environment problem, so the re-resolution assertion below it never runs -- the test is silent on the defect it exists to catch exactly when this fires. Filed distinct from #1014 (a fixed port-block collision in the smoke test) and #1210 arm 2 (FD/RSS provenance in the same probe). parse_items before/after: 306/226 open -> 307/227 open, the expected +1/+1/0 for filing one item. * backlog: file #1291 -- Get-HandledTools unrolls to NULL on two paths Measured in pwsh 7 via AST extraction: missing-file and zero-tool both return NULL; one tool returns a bare String, so .Contains() answers True on a substring of it. Filed as a LATENT trap, not a live defect. The single caller wraps in @(...), so shipped behaviour is correct today and a present-tense bug claim would be false. Recorded that way deliberately. Two details that decide how it gets fixed and tested: the missing-file early return unrolls the same as the tail, and that path is the normal one on a box where the gate was never installed; and a test through -Status cannot discriminate fixed from unfixed because the caller re-wraps, so the test must reach the function by AST extraction -- install-gate.ps1 cannot be dot-sourced without performing a machine-global install. parse_items 307/227 open -> 308/228 open, the expected +1/+1/0. * backlog: record #1290's same-head rerun confirmation Re-running the failed job alone on PR #447 head 4ccbf81, with no code change, returned completed/success. Identical commit, identical leg, opposite outcome, which settles nondeterminism more directly than the cross-branch pair the item was filed on. Bounded deliberately: it establishes that the failure is not a property of the tree under test, and establishes nothing about frequency or the runner load that triggers it, which is what sizing a fix actually needs. parse_items unchanged at 308/228 open, the expected 0/0/0 for amending a body. * backlog: file #1292 -- connscale smoke reports an acked message unobserved at intake The assertion names a CONFIRMED send that engine_read did not see. The harness already excludes the benign case in the same message (a send with no ACK before close is explicitly not counted as loss), so the remaining branch is either a harness reconciliation race or a count-and-log invariant failure. Filed because nobody has separated those two, and they have very different severities. Calling it a flake picks the comfortable branch without evidence. Red on main (run 32255231838, windows-2022) and on PR 448's rebased head (run 32259244062, ubuntu-latest), with different record shapes -- two platforms, two branches, which is evidence against a single fixed off-by-one. It already carries flaky(reruns=2) and failed anyway, so it exhausted its retries. Kept distinct from #1014 (port-block collision, same file) and #1290 (CPU-probe walk timeout, same suite). Two of the three sit inside required contexts, which is why the queue keeps stalling. Severity written in the conditional per CLAUDE.md section 0: zero instances, so nothing is affected today. P1 rests on the branch not yet ruled out. parse_items 308/228 -> 309/229, the expected +1/+1/0. * backlog: file #1293 -- worktree removal orphans unlanded ledger numbers owns() (ledger_check.py:219-231) compares the current worktree path to the one recorded at allocation, with no fallback when that directory is gone. Ownership is non-transferable, so an orphaned number can never be satisfied again. PR #397 is stranded by this now: #1264 was allocated by a worktree that no longer exists, is not on main, and #397's only conflict is docs/BACKLOG.md. Resolving re-introduces a heading absent from main, which counts as an addition, so ownership is consulted and refuses -- for everyone, permanently. The gate is failing closed and is correct. The defect is that there is no route back, and that the refusal only arrives after the resolution work is done. Records three candidate routes without picking one, and notes the fix must not be "widen the gate" -- the --ci leg already skips ownership by design. Also records the operational sweep that falls out: before removing a worktree, check whether it owns an allocation not yet on main. Run against the 16 worktrees proposed for removal today; returned zero, with #1264 as the positive control proving the sweep can detect the class. The first draft named a worktree slug and the forbidden-content gate refused it. Rewritten generically rather than allowlisted, per the rule that a refused slug is the gate working.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Split out of #447 at the owner's request. Independent of it: this branch is cut from
mainand does not carry the install-gate change. Found while sweepingscripts/for other instances of the unroll defect #447 fixes.The defect
Build-Mapreturns an empty array, PowerShell unrolls it to AutomationNull, and in process that is harmless -- assignment preserves it and the zero-rows guard still fires. It does not survive serialisation.ConvertTo-Jsonwrites"rows": null. The next run reads it back with@($c.rows), and that is a ONE-element array holding$null.Countis then 1, the guard does not fire, and the human report prints a phantom occupant: a blank worktree name on a blank branch with 0 changed files.Why it matters rather than merely being untidy
collision_gate.ps1runsoverlap.ps1 -Jsonon every gated edit, and the cache is written BEFORE the-Fileearly exit. So the hot path arms the cache constantly, and any bareoverlap.ps1inside the cache window inherits it. A session asking "who else is in this repo" is answered with an occupant that does not exist.An invented collision is not a safe direction to fail in. It is the answer people work around, and a report that cries wolf on a quiet repo is one they stop reading, which is how a real collision goes unnoticed.
This is the same class as the
Write-JsonArraynull filter already sitting above it in the same script, at the other end of the same round trip.The test
Driven against real throwaway git repos, because the question is entirely about what the script writes to disk and reads back. A test over stub rows would assert only that a value someone else computed gets carried, and the value is exactly what is wrong here.
Added to
tests/tooling_manifest.txtso it runs in the tooling tier alongside the other coord tests.test_tooling_partition.pyandtest_ci_tooling_gate.pypass with the addition.68 tests pass across the coord, collision-gate and manifest-partition files.
Generated with Claude Code