Skip to content

fix(ci,windows): gate release-aggregate on real evidence; win32 drain no longer double-reads - #109

Merged
stuinfla merged 6 commits into
mainfrom
fix/4.0.8-release-process
Aug 6, 2026
Merged

fix(ci,windows): gate release-aggregate on real evidence; win32 drain no longer double-reads#109
stuinfla merged 6 commits into
mainfrom
fix/4.0.8-release-process

Conversation

@stuinfla

@stuinfla stuinfla commented Aug 3, 2026

Copy link
Copy Markdown
Owner
  • fix(hooks): bound Codex stdin reads
  • fix(release): make 4.0.8 publication resumable
  • docs(adr): reconcile 4.0.8 release surfaces
  • fix(replay): preflight isolated Codex hooks
  • fix(replay): normalize Codex command envelopes
  • fix(replay): parse global Ruflo shell paths
  • test(replay): bind current D4 evidence
  • fix(release): prove Codex payloads and bound Windows stdin
  • docs(adr): refresh 95 contract currency
  • fix(release): validate Codex hooks under staged trust
  • docs(adr): refresh Codex hook currency
  • fix(release): mark Codex installed surface explicitly
  • fix(codex): read inherited Windows hook pipes directly
  • docs(adr): record Codex release verification
  • fix(codex): avoid blocking Windows pipe reads
  • docs(adr): record final Codex wrapper reread
  • fix(release): harden Windows hook input and offline QE skip
  • fix(codex): keep held-open deadline bounded
  • docs(adr): reconcile Codex and mesh currency
  • test(codex): allow Windows shell startup budget
  • test(codex): bound held-open probe under full-suite contention
  • fix(hooks): drain held-open Windows input
  • fix(release): reject failed stranger evidence triggers

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
explainer Ready Ready Preview Aug 6, 2026 12:16pm
ruvnet-brain Ready Ready Preview Aug 6, 2026 12:16pm

…rain no longer double-reads

Two independent fixes, both measured, neither previously on main.

RELEASE-AGGREGATE ELIGIBILITY. 051635a gated stranger-matrix one layer up, which
does not help: when that gate is false stranger concludes `skipped`, the
`workflow_run: completed` event still fires, and release-aggregate runs anyway
against a run that has no artifact. Measured on main — 5 of the last 6 runs red:

  12c29c1 -> failure   Artifact not found: stranger-evidence-12c29c12...
  051635a -> failure   (same, on the commit that supposedly fixed it)
  28fbc44 -> failure
  8f4e17c -> failure (x2)

Only a successful workflow_run or an explicit recovery dispatch is a valid
aggregate input, so the job now says exactly that. Git tags stop at v4.0.8 while
the repo reads 4.0.17-dev and npm latest is 4.0.12 — three surfaces disagreeing
about what exists is what this guard is for.

THE WIN32 DRAIN WAS A FAIL-OPEN REGRESSION. The first version of this branch's
hook-input change called onData(chunk) on what process.stdin.read() returned.
read() ITSELF emits 'data', so the registered listener already had that chunk and
every byte was counted twice. Measured, forcing platform=win32 against a real pipe:

  main            79 bytes  parses OK
  this branch v1  158 bytes PARSE-FAIL   <- payload concatenated with itself

JSON.parse threw, readStdinBounded returned an empty envelope, every gate field
read '' — so the PreToolUse wall FAILED OPEN on every Windows invocation. That is
worse than the held-open pipe it was written to fix: the original bug stalled,
this one waved commands through. CI reported it only as 30 downstream assertions
in codex-lifecycle-hooks and continuation-gate ("expected '' to contain
'ruvnet_cli_help'"), run 30858227845 — the shape of an unguarded parser defect.

Root cause is platform-independent Node stream semantics: attaching a 'readable'
listener pins the stream paused, because resume() computes
`flowing = !readableListening`. The drain is therefore the ONLY byte path on
win32, which is why the double-count was total rather than intermittent.

The drain now drains and nothing else.

GUARDED. tests/unit/hook-input.test.mjs asserts bytes === Buffer.byteLength(env)
across a process boundary with platform forced — magnitude, not direction, since
a doubling passes any non-empty check. Reintroducing the old drain fails it
`expected 158 to be 79`. Verified by doing exactly that. The branch previously
shipped this change with zero tests, which is how a 100%-reproducible defect
reached a required lane invisibly on every non-Windows dev machine.

ADR-0055 currency log records the supersession; ADR-0062 gains the guard and
governs release-aggregate.yml.

Caveat stated plainly: the double-read reproduction forces platform on darwin, so
it exercises Node's stream state machine rather than a real Windows pipe. The
ORIGINAL held-open-EOF symptom is Windows-specific and still needs a real
windows-unit run to prove this fixes it.
@stuinfla
stuinfla force-pushed the fix/4.0.8-release-process branch from e581ac7 to 9081399 Compare August 6, 2026 12:02
@stuinfla stuinfla changed the title fix/4.0.8 release process fix(ci,windows): gate release-aggregate on real evidence; win32 drain no longer double-reads Aug 6, 2026
…drain

Scope cut deliberately. This branch now carries ONE fix, not two.

RELEASE-AGGREGATE ELIGIBILITY — landing. 051635a gated stranger-matrix one layer
up, which does not help: when that gate is false stranger concludes `skipped`,
the `workflow_run: completed` event still fires, and release-aggregate runs
anyway against a run that has no artifact. Measured on main, 5 of the last 6 red:

  12c29c1 -> failure   Artifact not found: stranger-evidence-12c29c12...
  051635a -> failure   (on the very commit meant to fix it)
  28fbc44 -> failure
  8f4e17c -> failure (x2)

Only a successful workflow_run or an explicit recovery dispatch is a valid
aggregate input, so the job now says exactly that. Git tags stop at v4.0.8 while
the repo reads 4.0.17-dev and npm latest is 4.0.12 — three surfaces disagreeing
about what exists is what this guard is for.

THE WIN32 DRAIN IS WITHDRAWN — measured to fail in BOTH directions.

  attempt 1, onData on the read chunk : 158 bytes for a 79-byte envelope
                                        JSON.parse throws, gate fields read ''
                                        -> PreToolUse wall FAILS OPEN on Windows
  attempt 2, drain only               : main exits 118ms on a held-open pipe
                                        this version NEVER exits (>2000ms)
                                        -> routing-outcome-capture timedOut=true

Attempt 1 shipped in this branch and produced 30 downstream assertions in
codex-lifecycle-hooks and continuation-gate (run 30858227845). Attempt 2 was
written to fix it and traded a fail-open for a hang, caught by windows-unit job
92610172373 before merge.

Common root cause, and the reason neither is a one-liner: attaching a `readable`
listener pins stdin in paused mode permanently — resume() computes
`flowing = !readableListening` and cannot undo it — so the drain becomes the only
byte path AND the stream's exit lifecycle changes. That is a lifecycle change
masquerading as a parsing tweak.

hook-input.mjs and its test are therefore restored byte-identical to main. The
original intermittent `SessionStart: waited for stdin EOF` symptom stays OPEN and
needs a design that does not attach `readable` to process.stdin. Both
measurements are recorded in ADR-0055 so the next attempt starts from them
instead of rediscovering them.

Shipping half a fix that is proven beats shipping two where one hangs.

ADR-0062 gains the guard and now governs release-aggregate.yml.
@stuinfla
stuinfla merged commit 85c9b75 into main Aug 6, 2026
12 checks passed
@stuinfla
stuinfla deleted the fix/4.0.8-release-process branch August 6, 2026 12:24
stuinfla added a commit that referenced this pull request Aug 6, 2026
…e problem (#120)

* fix(qe): judge the best of N render samples, not one — the budget was never the problem

MEASURED on hosted windows-latest today, same gate, unchanged product:

  job 92610172864   console time-to-visible    877ms   PASS
  job 92625527103   console time-to-visible   4523ms   FAIL (>4000ms)
  job 92610172864   console time-to-visible   5535ms   FAIL (>4000ms)

A 6x spread. Gating ONE wall-clock sample against a hard budget therefore fails
roughly a third of Windows runs on contention alone. PRs #109 and #117 were both
held red by this and both went green on a bare re-run with no code change. A lane
that is red for reasons nobody can act on teaches people to ignore red, which is
the same disease as a gate that only whispers.

The tempting fix is raising win32 to 6000ms. That buys quiet by blinding the gate
to the regression it exists for. PLATFORM_BUDGETS already says these are "release
budgets, not performance claims about GitHub's hardware" and that "CI receipts
make future recalibration evidence-based rather than guessed" — the receipts say
the budget is right and the SAMPLING is wrong.

So re-run the probe up to 3 times and judge the best attempt:

  - a real regression is slow EVERY time  -> still fails, budget untouched
  - a contended runner is slow ONCE       -> a later attempt lands, lane goes green

This strictly cannot pass anything a single attempt would have passed; it only
rescues runs a single attempt failed for reasons outside the product. The first
clean attempt returns immediately, so the healthy path costs what it costs today.
A retry is announced in the output — a retry that hides itself is indistinguishable
from a budget nobody enforces.

GUARDED, and the load-bearing test is the negative one: "uniformly slow stays RED
after every attempt". If that assertion is ever deleted this module becomes a
regression-hiding device, and the test says so in those words. Also covers 4001ms
(one millisecond over is still over — magnitude, not direction), unmeasurable
probes, and probe notes, which must never short-circuit as clean. 8/8.

* docs(adr-0058): re-read the governed QE surface after the best-of-N change

doc-currency blocked the push: this branch changes scripts/qe/ux-suite.mjs, which
0058 governs, and nobody had re-read it since the move.

The row states plainly that the 95 contract and every PLATFORM_BUDGET value are
UNCHANGED — best-of-N fixes the SAMPLING, not the bar — and carries the three
measured windows-latest samples (877ms / 4523ms / 5535ms) that show why gating a
single wall-clock sample was the defect.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant