← board

gate.sh self-host reads the live compiler/pascal26, so a concurrent build flakes it

What happens

selfhost_fixedpoint.sh checks two properties. The second — the anti-Thompson one — is that the fixedpoint reached from pinned must equal compiler/pascal26, the binary the suite is testing with. It reads that binary at the live, mutable path.

compiler/pascal26 is a single mutable path and a prerequisite of every test target, so any concurrent make in the same clone replaces it mid-check. Then:

FAIL: the fixedpoint reached from PINNED differs from compiler/pascal26
      /tmp/selfhost-fp-2165496/stage_1a /home/neo/pxx/compiler/pascal26 differ: byte 97
gate: RED

Observed once on plexus with 17 other build processes on the box (the watcher's clone plus a sibling agent). make compiler/pascal26 then reported "up to date", selfhost_fixedpoint.sh re-run reported "converged after 1 round(s) ... agrees with compiler/pascal26", and gate.sh quick was GREEN. Nothing was wrong; the binary simply changed underfoot.

Why it matters

The property is RIGHT and worth keeping — it is the only check that catches a compiler converging to a different self-reproducing fixedpoint. But a gate that is red for the normal case trains agents to ignore it, which is the exact complaint that filed bug-t-gate-sh-fixedpoint-does-not-iterate. Trading a deterministic false red for an intermittent one is not a win.

It also lands hardest where clones are shared: the box that runs the watcher is also the box an agent gates on.

Fix shape

tools/testmgr.py already solved this — it takes the run's own copy of the compiler (RUN_TMP) precisely because "any unrelated make can replace it mid-run". Do the same here: snapshot compiler/pascal26 once at the start of selfhost_fixedpoint.sh and compare against the snapshot, so the answer describes one instant.

Cheaper alternative if a snapshot is unwanted: re-read and re-compare once on mismatch, and report the flake distinctly (compiler/pascal26 changed during the check — rerun) rather than as a self-host failure. That keeps the signal honest without a copy, but it is a retry, not a fix.

Gate

gate.sh quick green; a devtest that replaces compiler/pascal26 mid-check must not produce a self-host FAIL.

DONE 2026-08-14 — snapshot, with the torn-read hazard the ticket missed

Took the ticket's preferred fix (snapshot, not retry): selfhost_fixedpoint.sh copies compiler/pascal26 once before the rounds start and judges property 2 against that copy, so the verdict describes one instant.

The ticket's fix shape needed one addition. A plain cp can capture a half-written binary — the same hazard testmgr documents for its own snapshot — which would swap the reported false red for a different one, harder to recognise. So the snapshot hashes the live file either side of the copy and retries until the two agree; three torn attempts means a build is actively running, and the agreement check is skipped with a NOTE rather than guessed at. Convergence, the real gate, still runs.

A second thing surfaced while testing: with a stable snapshot the concurrent replacement becomes invisible — the comparison never sees it, so the run is simply green. The "changed during the check" message therefore fires only in the narrower case where the snapshot genuinely disagrees AND the live binary has since moved. Both paths are covered; the first devtest asserted the fallback for the primary case and passed for the wrong reason until that was split.

Gate

tools/devtest_selfhost_race.sh — 14 checks against a scratch tree of self-reproducing stub "compilers", so every branch runs in ~10s instead of ~40s per case against the real compiler, and nothing touches the live binary while the watcher may be building:

case asserted
agreement rc 0, says so
genuine mismatch (stable, different) rc 1, reported as self-host FAIL — the Thompson check must not be softened
replaced mid-check, BUILT was correct rc 0, no FAIL, agreement still asserted from the snapshot
BUILT stale AND changed under us rc 0, named as the race, not as self-host
never converges rc 1, reported as a self-host regression
no compiler/pascal26 at all rc 0, and claims no agreement it did not check

tools/gate.sh quick GREEN. Note the real run also caught a genuine stale binary while this was being written (a sibling landed e5702ed75 mid-session), which is the check doing its job — the fix does not blunt it.

Log