Skip to content

Commit 390ddbb

Browse files
committed
ci: roll out run-with-stall-guard beyond Test Core (#4314)
#4309 gave Test Core the output stall guard; the same #4250 failure mode elsewhere still presented as hang-until-timeout. Extend the guard to every remaining suite-running job: - Dogfood shards (ci.yml): the guard replaces the `| tee dogfood.log` + pipefail idiom (log path unchanged, so the completeness guard needs no edit) and declares a stall at 10 min of silence. Job timeout drops 45 -> 30 as a pure backstop, mirroring Test Core. Dogfood boots real engines in-process -- exactly the population #4250's stalls came from. - rerun-safety-nightly: both full passes run under the guard at 15 min (a nightly nobody watches previously hung until the 120-min timeout). Pass 2 now tells the two reds apart: guard exit 75 gets a stall verdict pointing at #4250, everything else keeps the #4065 rerun-unsafe text -- a stall says nothing about working-tree pollution, and the old copy would have sent triage hunting for state leaks that aren't there. - coverage-nightly: wrapped at 10 min; trivial cost. No `... | tee` + pipefail combination remains in suite-running workflows (the `find`-based dist check in ci.yml keeps its pipefail; it is not this pattern). Closes #4314 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NNaWXVEyG6Pv4EFRfYGg6
1 parent e5a4d26 commit 390ddbb

4 files changed

Lines changed: 47 additions & 11 deletions

File tree

.changeset/stall-guard-rollout.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
ci: roll out run-with-stall-guard to the Dogfood shards, rerun-safety-nightly, and coverage-nightly (#4314). CI-only — releases nothing.

.github/workflows/ci.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,10 @@ jobs:
378378
needs: filter
379379
if: needs.filter.outputs.core == 'true'
380380
runs-on: ubuntu-latest
381-
timeout-minutes: 45
381+
# Backstop only — the stall guard on the test step is the primary detector
382+
# for a #4250-style hang (see Test Core). 30 min is ~2.5× the slower shard
383+
# (shard 1 runs the verify-CLI step too, ~12 min all in).
384+
timeout-minutes: 30
382385
permissions:
383386
contents: read
384387
strategy:
@@ -436,11 +439,16 @@ jobs:
436439
# break, which was green on every static gate). The `--` args reach the
437440
# package's `vitest run` and are hashed into the turbo task, so each
438441
# shard caches independently.
442+
# run-with-stall-guard: same wiring as Test Core (see the comment there;
443+
# #4250/#4314) — it tees to dogfood.log itself for the completeness guard
444+
# below, propagates the suite's real exit status (no `| tee` + pipefail),
445+
# and turns frozen output into a labeled red after 10 min of silence.
446+
# Dogfood boots real engines in-process — exactly the population #4250's
447+
# stalls came from.
439448
- name: Boot example apps and exercise real user flows
440449
run: |
441-
set -o pipefail
442-
pnpm turbo run test --filter=@objectstack/dogfood -- --shard=${{ matrix.shard }}/2 \
443-
2>&1 | tee "$RUNNER_TEMP/dogfood.log"
450+
node scripts/run-with-stall-guard.mjs --log "$RUNNER_TEMP/dogfood.log" --stall-minutes 10 -- \
451+
pnpm turbo run test --filter=@objectstack/dogfood -- --shard=${{ matrix.shard }}/2
444452
445453
# Dogfood boots real apps in-process, so a native/OOM abort is likelier
446454
# here than in the unit suites — and a shard that dies silently looks like

.github/workflows/coverage-nightly.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ jobs:
5252
# The spec package sits at the root of the workspace graph (no workspace
5353
# deps) and its suite reads src/ directly, so no build step is needed —
5454
# same reason lint.yml's typecheck gates run pre-build.
55+
# Stall guard (#4250, #4314): a frozen-output hang becomes a labeled red
56+
# after 10 min of silence instead of sitting until the job timeout.
5557
- name: Generate coverage report
56-
run: pnpm --filter @objectstack/spec test:coverage
58+
run: |
59+
node scripts/run-with-stall-guard.mjs --log "$RUNNER_TEMP/spec-coverage.log" --stall-minutes 10 -- \
60+
pnpm --filter @objectstack/spec test:coverage
5761
5862
- name: Upload coverage report
5963
uses: actions/upload-artifact@v7

.github/workflows/rerun-safety-nightly.yml

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ jobs:
4141
runs-on: ubuntu-latest
4242
# Two full passes of everything, dogfood included. Generous, but bounded —
4343
# an unbounded job is what let a hung Test Core sit for GitHub's 6h default
44-
# while the PR read as "still running" rather than broken.
44+
# while the PR read as "still running" rather than broken. Since #4314 the
45+
# primary stall detector is run-with-stall-guard on each pass (labeled red
46+
# after 15 min of frozen output); this is the pure backstop.
4547
timeout-minutes: 120
4648
steps:
4749
- name: Checkout repository
@@ -77,9 +79,14 @@ jobs:
7779

7880
# Deliberately NOT `continue-on-error`. If pass 1 is red the suite is
7981
# simply broken on main and that is worth failing on; this gate adds pass
80-
# 2, it does not replace the ordinary signal.
82+
# 2, it does not replace the ordinary signal. The stall guard (#4250,
83+
# #4314) bounds a frozen-output hang at 15 min instead of letting a
84+
# nightly nobody watches sit until the 120-min timeout; on a stall it
85+
# exits 75 and its banner in the log names the last output line.
8186
- name: Test suite — pass 1
82-
run: pnpm turbo run test --concurrency=4 --force
87+
run: |
88+
node scripts/run-with-stall-guard.mjs --log "$RUNNER_TEMP/rerun-pass1.log" --stall-minutes 15 -- \
89+
pnpm turbo run test --concurrency=4 --force
8390
8491
# Informational only. A stray `.objectstack/` is how the #4065 class shows
8592
# up on disk, so printing what pass 1 left behind turns a pass-2 failure
@@ -95,14 +102,27 @@ jobs:
95102
echo "Files inside them:"
96103
find . -path ./node_modules -prune -o -type d -name '.objectstack' -exec find {} -type f \; || true
97104
105+
# A pass-2 red normally means rerun-UNSAFE — but a stall (guard exit 75)
106+
# says nothing about working-tree pollution, and the #4065 error text
107+
# would send whoever triages it hunting for state leaks that aren't
108+
# there. Keep the two verdicts apart.
98109
- name: Test suite — pass 2 (same working tree)
99110
run: |
100-
set -o pipefail
101-
if ! pnpm turbo run test --concurrency=4 --force; then
111+
status=0
112+
node scripts/run-with-stall-guard.mjs --log "$RUNNER_TEMP/rerun-pass2.log" --stall-minutes 15 -- \
113+
pnpm turbo run test --concurrency=4 --force || status=$?
114+
if [ "$status" -eq 0 ]; then
115+
exit 0
116+
elif [ "$status" -eq 75 ]; then
117+
echo "::error::Pass 2 STALLED — frozen output, killed by run-with-stall-guard"
118+
echo "::error::(see #4250). This is NOT a rerun-safety verdict: a stall says"
119+
echo "::error::nothing about working-tree pollution. Rerun the workflow; if it"
120+
echo "::error::stalls again at the same file, note the occurrence on #4250."
121+
else
102122
echo "::error::The suite passes once and fails on a second run in the same"
103123
echo "::error::working tree. Something under test writes state into the tree and"
104124
echo "::error::reads it back on the next run. Ordinary CI cannot see this — every"
105125
echo "::error::other job is a fresh clone, so every other job is always run #1."
106126
echo "::error::See the on-disk state reported above, and #4065 for the pattern."
107-
exit 1
108127
fi
128+
exit "$status"

0 commit comments

Comments
 (0)