A probe that flakes once during a gate sweep is absorbed into the baseline as an accepted failure nf-m4m0 ← Beads

closed priority 2 task unassigned activesession:probe-gate

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

Hit live on the #505 merge. Filed unclaimed.

What happened

merge_pr.sh 505 ran the post-merge UI gate and reported:

``` ──── UI gate: main @ d3d90d9 ──── 31 passed, 1 failed compared against the sweep recorded at f5fd75f

████ NEW FAILURES — this merge regressed main ████ new-pattern [probe-gate] recorded baseline at d3d90d9 (1 failing) ```

#505 cannot have caused it. Its entire diff is tools/pr/rebuild_f5.py, tools/pr/poll.py, their tests, and CLAUDE.md — no game code, no scene, no probe. And new-pattern passes on that exact sha:

``` $ git log --oneline -1 d3d90d9 rebuild_f5: hold paused PRs out of f5 ... (#505) $ for i in 1 2 3; do ./probe.sh tools/probes/new-pattern.json; done run 1: exit=0 | 0 assertion-failures run 2: exit=0 | 0 assertion-failures run 3: exit=0 | 0 assertion-failures ```

3/3 green standalone, failed once inside a 32-probe sweep. It is flaky under sweep load, not regressed.

Why the flake, specifically

tools/probes/new-pattern.json is entirely frame-counted — {"wait": 45} for the scene to load and seed the demo, {"wait": 10} / {"wait": 14} after opening the naming modal. Nine wait steps, zero wait_ms.

VisualProbe.cs:213-215 names this exact hazard: *"the off-screen probe window runs uncapped under load, so 40 frames can be under 200ms"*. During a full sweep the box is busy, and 45 frames is no longer the wall-clock the probe was calibrated against. Same class ninefold-reviewer flagged as F1 on #508 hours ago, biting main's gate rather than one PR.

The part that actually matters

The flake is now the baseline. The gate recorded d3d90d9 (1 failing) including new-pattern, so from here on a *genuine* break in that probe is compared against "already failing" and never surfaces as NEW. The gate is off for that probe until something re-records it green.

And it cannot be corrected at that sha, by design:

``` $ python3 tools/pr/probe_gate.py [probe-gate] d3d90d9 is already recorded — skipping the sweep (1 failing at that sha). new-pattern (since d3d90d9) [probe-gate] (another session swept this exact tree; re-sweeping would re-derive [probe-gate] a known answer, and a flaky probe could turn it into a false NEW.) ```

That guard is right in general — and here it is the thing preserving the false entry. The (since <sha>) tracking shows persistence is intended, which is correct for a real failure and wrong for a flake.

Self-healing on the next merge *if* the probe passes in that sweep. Not guaranteed, since the flake is load-dependent and merges are exactly when the box is loaded.

Worth considering

The first is the one that stops the class; the rest are cleanup.

Related: #508 (F1, same frames-vs-wall-clock class), #389 (a gate that rotted unnoticed), #409 (needs:direction on probe-gate reporting), #412.

Dependencies

None.

Comments

No comments.

Add a comment