You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CI: the ~55-min quickstart testnet step-8 hang recurred on #3765 attempt 4 — #3289's step-level timeout-minutes: 25 did not fire, and the job cancel destroyed the watchdog dump it was built to capture #3768
The ~55-min step-8 hang that #3272 → #3273 → #3286 → #3287 → #3289 chased across
three fixes recurred on PR #3765, attempt 4, with the same duration signature —
and #3289's step-level timeout-minutes: 25did not fire. The job died on the
45-min job-level wall-clock instead, which is a cancel, so:
The instrumentation added specifically to capture this hang was destroyed by the
hang it was built to capture. That is the part worth acting on: this attempt
produced less evidence than the pre-#3289 attempts did.
.github/workflows/quickstart.yml, the design intent (lines ~455–465):
DIAGNOSTIC instrumentation (#3286): this shard also sets a TIGHT, testnet-only
step-level fail-fast bound via step_timeout_minutes: 25 … Two wrapper-level
fixes (#3273, #3287) both FAILED to bound the ~55-min step-8 hang, and a 45-min
JOB-level wall-clock is a cancel — so if: failure() never fires and the
diagnostics step never uploads. A step-level timeout-minutes kill instead marks
the STEP failed (distinct from a job cancel), so the upload step runs.
Attempt 4 is that exact failure mode, after the fix.
Evidence
Run 30329850375,
job 90210359873, PR #3765 (branch do/issue-3756, head 9b1f1314):
attempt=1 id=90187502593 failure 1640 s (27.3 min) start 05:23:58Z
attempt=2 id=90191646140 failure 1070 s (17.8 min) start 05:51:30Z
attempt=3 id=90196608534 failure 1600 s (26.7 min) start 06:22:07Z
attempt=4 id=90210359873 cancelled 3300 s (55.0 min) start 07:36:52Z ← this one
Per-attempt step conclusions for Run probes through wrapper and the upload that #3289 exists to enable:
attempt
step secs
step conclusion
Upload diagnostics on failure
1
1590 (26.5 min)
failure
success
2
1036 (17.3 min)
failure
skipped
3
1544 (25.7 min)
failure
success
4
—
none
none (never ran)
Attempts 1 and 3 land at 1590 / 1544 s — the 25-min (1500 s) step bound plus ~45–90 s
of overshoot — and the upload runs. That is #3289 working. Attempt 4 does not.
(Attempt 2 is a separate oddity: the step failed at 17.3 min, comfortably under the
bound, yet Upload diagnostics on failure shows skipped rather than running. I have
not chased that and am not claiming a cause; noting it so it isn't mistaken for the
attempt-4 signature.)
The 45-min job timeout fired, and needed the full 10-min hard grace
Exact to the second, on both bounds. The 10-min overshoot past 45 min is the runner
being unable to terminate the step promptly — the same unkillability that explains
why the 25-min step bound produced nothing.
Step conclusions
step secs conclusion
Set up job 3 success
Run actions/checkout@v4 4 success
Download quickstart image 13 success
Load Docker image 27 success
Checkout stellar/quickstart (tests) 1 success
Run actions/setup-go@v5 1 success
Run quickstart container 1 success
Run probes through wrapper -- None ← never concluded
Upload diagnostics on failure -- None ← never ran
Cleanup -- None
Post Run actions/setup-go@v5 -- None
Post Checkout stellar/quickstart (tests) -- None
Post Run actions/checkout@v4 -- None
Run probes through wrapper has no completed_at and no conclusion. A step-level timeout-minutes kill sets the step to failure; this step was never concluded at
all, so the 25-min bound did not act on it.
No logs exist
$ gh api repos/stellar-experimental/henyey/actions/jobs/90210359873/logs
<Error><Code>BlobNotFound</Code><Message>The specified blob does not exist.
Attempts 1–3 have logs; attempt 4 has none. So for the one attempt that reproduced
the full ~55-min hang, there is no step output, no probe timings, no soft-skip
markers, and no watchdog artifact.
Two candidate explanations, and what I ruled out
I can show the 25-min bound did not fire. I cannot show why, and there are no
attempt-4 logs to discriminate. The two live candidates:
The step was uninterruptible. The runner issued the step kill at ~08:02:50 and
could not complete it, so no conclusion was recorded; the job timeout at 08:21:52
also could not complete, and only the 10-min hard grace terminated the runner. The
10-min overshoot is direct evidence for the second half of this.
Runner/infra fault independent of the hang.
Ruled out:
Concurrency cancel.quickstart.yml:113 sets cancel-in-progress: false, and gh run list shows exactly one quickstart run on do/issue-3756. Nothing
superseded it.
Per-probe budget scaling with run_attempt. I hypothesised the wrapper's PROBE_TIMEOUT=$(( github.run_attempt * timeout_multiplier * 60 )) (line ~544)
reached 4 × 4 × 60 = 960 s on attempt 4 and blew the step budget. Falsified —
lines 549–551 override it unconditionally on this shard:
if [[ -n"$SHARD_PROBE_TIMEOUT" ]];then
PROBE_TIMEOUT="$SHARD_PROBE_TIMEOUT"fi
with SHARD_PROBE_TIMEOUT: ${{ matrix.probe_timeout }} = 240. The per-probe
budget is 240 s on every attempt. Attempt number is not the variable.
Why this matters beyond one red check
PR #3765 is MERGEABLE with every other check green — Clippy, Format, Doctest, Build (release-ci), Test, all Analyze jobs, Monitor-Tick Skill Snippets, Quickstart Harness, Soroban Pin Check. The sole red is this shard.
#3765 offloads the peer_refresh arm behind #3756. On the production validator this
morning the event-loop park p95 is 23,951 ms against a 30,000 ms busy_timeout
ceiling, fitted at ~853 ms/day (out-of-sample validated to −17 ms), which puts the
p95 at the ceiling around 2026-08-03. So the shard is currently the gate in front
of a fix with a dated deadline, and it has now failed four times for reasons the
shard itself documents as environmental.
#3290 was closed NOT_PLANNED with the reasoning that "the soft-skip resolves each
probe at its 240s bound, the step-level 25-min timeout (#3289) is a hard backstop."
I posted evidence yesterday that on attempts 1/3 the backstop fires but arrives with
no budget left for the remaining probes. Attempt 4 is a stronger statement:
attempts 1, 3
attempt 4
25-min step bound
fires (step = 1590 / 1544 s)
does not fire
diagnostics uploaded
yes (success)
no (never ran)
logs retained
yes
no (BlobNotFound)
terminated by
step timeout-minutes
job timeout-minutes + 10-min grace
A backstop that fires on 3 of 4 attempts is not a backstop for the case it was built
for. I'm recording that here rather than reopening #3290 — reversing a deliberate NOT_PLANNED is an operator call, and this is input to it.
Lower WATCHDOG_DELAY and upload eagerly. The dump at ~07:58Z existed on disk
for 34 minutes and was thrown away because the upload is gated on if: failure().
Uploading from the watchdog itself (or on if: always()) would have salvaged it.
Attempt 2 (17.8 min, failure) is under the step bound and therefore a
different signature from 1/3/4 — worth reading its log for a genuine assertion
failure before treating all four as environmental.
Finding
The
~55-min step-8 hangthat #3272 → #3273 → #3286 → #3287 → #3289 chased acrossthree fixes recurred on PR #3765, attempt 4, with the same duration signature —
and #3289's step-level
timeout-minutes: 25did not fire. The job died on the45-min job-level wall-clock instead, which is a cancel, so:
Upload diagnostics on failurenever ran,WATCHDOG_DELAY=1200s≈ 20 min,i.e. ~07:58Z, well before the end) was lost,
The instrumentation added specifically to capture this hang was destroyed by the
hang it was built to capture. That is the part worth acting on: this attempt
produced less evidence than the pre-#3289 attempts did.
.github/workflows/quickstart.yml, the design intent (lines ~455–465):Attempt 4 is that exact failure mode, after the fix.
Evidence
Run 30329850375,
job
90210359873, PR #3765 (branchdo/issue-3756, head9b1f1314):Per-attempt step conclusions for
Run probes through wrapperand the upload that#3289 exists to enable:
Upload diagnostics on failurefailuresuccessfailureskippedfailuresuccessAttempts 1 and 3 land at 1590 / 1544 s — the 25-min (1500 s) step bound plus ~45–90 s
of overshoot — and the upload runs. That is #3289 working. Attempt 4 does not.
(Attempt 2 is a separate oddity: the step failed at 17.3 min, comfortably under the
bound, yet
Upload diagnostics on failureshowsskippedrather than running. I havenot chased that and am not claiming a cause; noting it so it isn't mistaken for the
attempt-4 signature.)
The 45-min job timeout fired, and needed the full 10-min hard grace
Exact to the second, on both bounds. The 10-min overshoot past 45 min is the runner
being unable to terminate the step promptly — the same unkillability that explains
why the 25-min step bound produced nothing.
Step conclusions
Run probes through wrapperhas nocompleted_atand no conclusion. A step-leveltimeout-minuteskill sets the step tofailure; this step was never concluded atall, so the 25-min bound did not act on it.
No logs exist
Attempts 1–3 have logs; attempt 4 has none. So for the one attempt that reproduced
the full ~55-min hang, there is no step output, no probe timings, no soft-skip
markers, and no watchdog artifact.
Two candidate explanations, and what I ruled out
I can show the 25-min bound did not fire. I cannot show why, and there are no
attempt-4 logs to discriminate. The two live candidates:
could not complete it, so no conclusion was recorded; the job timeout at 08:21:52
also could not complete, and only the 10-min hard grace terminated the runner. The
10-min overshoot is direct evidence for the second half of this.
Ruled out:
quickstart.yml:113setscancel-in-progress: false, andgh run listshows exactly one quickstart run ondo/issue-3756. Nothingsuperseded it.
run_attempt. I hypothesised the wrapper'sPROBE_TIMEOUT=$(( github.run_attempt * timeout_multiplier * 60 ))(line ~544)reached
4 × 4 × 60 = 960 son attempt 4 and blew the step budget. Falsified —lines 549–551 override it unconditionally on this shard:
SHARD_PROBE_TIMEOUT: ${{ matrix.probe_timeout }}=240. The per-probebudget is 240 s on every attempt. Attempt number is not the variable.
Why this matters beyond one red check
PR #3765 is MERGEABLE with every other check green —
Clippy,Format,Doctest,Build (release-ci),Test, allAnalyzejobs,Monitor-Tick Skill Snippets,Quickstart Harness,Soroban Pin Check. The sole red is this shard.#3765 offloads the
peer_refresharm behind #3756. On the production validator thismorning the event-loop park p95 is 23,951 ms against a 30,000 ms
busy_timeoutceiling, fitted at ~853 ms/day (out-of-sample validated to −17 ms), which puts the
p95 at the ceiling around 2026-08-03. So the shard is currently the gate in front
of a fix with a dated deadline, and it has now failed four times for reasons the
shard itself documents as environmental.
Bearing on #3290
#3290 was closed
NOT_PLANNEDwith the reasoning that "the soft-skip resolves eachprobe at its 240s bound, the step-level 25-min timeout (#3289) is a hard backstop."
I posted evidence yesterday that on attempts 1/3 the backstop fires but arrives with
no budget left for the remaining probes. Attempt 4 is a stronger statement:
success)BlobNotFound)timeout-minutestimeout-minutes+ 10-min graceA backstop that fires on 3 of 4 attempts is not a backstop for the case it was built
for. I'm recording that here rather than reopening #3290 — reversing a deliberate
NOT_PLANNEDis an operator call, and this is input to it.Suggested next steps
so far (
timeout -kin the wrapper Soft-skip testnet quickstart probe timeouts to de-gate main #3273, orphan-pipe bounding Bound quickstart probe wrapper on orphaned stdout-pipe holders (#3286) #3287, steptimeout-minutesInstrument testnet quickstart hang: fail-fast + process-tree watchdog (#3286) #3289) requires the hung process to be killable from inside therunner. A sidecar that writes the dump on a timer and uploads it independently of
the step's exit — or
docker killfrom a separate step — would survive a jobcancel.
WATCHDOG_DELAYand upload eagerly. The dump at ~07:58Z existed on diskfor 34 minutes and was thrown away because the upload is gated on
if: failure().Uploading from the watchdog itself (or on
if: always()) would have salvaged it.failure) is under the step bound and therefore adifferent signature from 1/3/4 — worth reading its log for a genuine assertion
failure before treating all four as environmental.
(CI testnet quickstart: cap cumulative probe wall-clock / decide whether the shard should gate at all (operator policy) #3290 option 2, the History Publish (Testnet): timed out waiting for first published checkpoint (regression 06-09..06-10) #3280 History-Publish precedent) or an admin merge is a policy
call, but it should be made with the 08-03 date in view rather than by re-running a
fifth time.
Not labelled urgent
CI, not a validator symptom, per the label policy. Flagging the coupling explicitly
because the blocked PR has a dated deadline.
Related: #3272, #3273, #3286, #3287, #3289, #3290 (the hang lineage), #3756 (the fix
this blocks), #3764 (same shard, same day).
Surfaced by
/monitor-tick2827.