Skip to content

Split unit-test.yml's Run tests step into one step per suite - #2588

Merged
kriszyp merged 1 commit into
mainfrom
ci/split-unit-test-steps
Sep 14, 2026
Merged

kriszyp merged 1 commit into
mainfrom
ci/split-unit-test-steps

Conversation

@kriszyp

@kriszyp kriszyp commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

Summary

The unit-test job's Run tests step chained npm run test:unit:all (main && apitests && resources && lmdb) behind one 15-minute timeout (unit-test.yml:87-89 on main). A hang or timeout in any one suite killed the step, so every later suite was silently skipped and the checks list gave no way to tell which suite actually died.

This splits it into one named step per suite, each with its own timeout. The three later steps run whenever Setup Harper succeeded, regardless of an earlier suite's outcome, so one bad suite no longer suppresses the others — but they still skip on cancellation or on any setup failure upstream, since a skipped step's outcome is never 'success'.

Per-step timeouts are sized off measured Node 22 durations (the slowest matrix leg), taken from three recent runs' logs (main's most recent green run plus the two branch runs referenced in the issue): main ~2m15-2m30 → 4m cap, apitests ~20-25s → 3m cap, resources ~4m30-5m25 → 7m cap, lmdb ~1m55-2m05 → 4m cap.

For the human reviewer

Five rounds of cross-model review (codex + gemini + cursor-composer + harper-domain) surfaced and fixed a real gap — the continuation guard originally used bare if: always(), which also fires on job cancellation and on any setup failure, not just a test-suite failure. All four suite steps now gate on if: "!cancelled() && steps.setup-harper.outcome == 'success'" instead, so they skip cleanly when setup itself is broken or the run is cancelled.

Two findings kept resurfacing that I'm deliberately not fixing, because the fix conflicts with this task's acceptance criteria (harper#2587 explicitly says don't touch the job-level timeout-minutes):

  • Job-cap margin shrank from ~4 minutes to ~1 minute. Before, only the single combined Run tests step's timeout (15m) could be "wasted" by a wedge, capping the worst case at 26 (setup steps) + 15 = 41 against the 45-minute job cap. Now that later suites run regardless of an earlier suite's failure, the pathological case — every suite independently maxing its own cap — sums to 26 + 18 = 44, one minute under the job cap. If that's ever hit, the job reports cancelled (grey) instead of a step failure (red) on a required check, which is exactly the outcome the job's own top-comment says the per-step caps exist to avoid. The reviewers' fix is to raise timeout-minutes to 48-50; the issue explicitly rules that out. I considered shrinking the suite caps further instead, but that just relocates the same false-timeout risk onto whichever suite gets squeezed (see next point) — there's no way to both keep per-suite caps "generous" and restore the old margin under a fixed 45-minute ceiling, since test:unit:resources alone needs at least ~6-7 minutes to safely cover its own measured tail.
  • test:unit:resources's 7-minute cap has the thinnest headroom of the four (~29% over its measured 5m25 max, vs 60-620% on the others). I sized it off three data points that were fairly consistent (4m33s-5m25s); widening it further would need to come from somewhere else in the 44-minute budget, reopening the point above.

One more open item: the workflow now hardcodes the four-suite list inline instead of just invoking npm run test:unit:all, so nothing reconciles the two if package.json:73 ever grows a fifth suite. That's inherent to giving each suite its own step/timeout/name — happy to add a comment pointer or a lint check in a follow-up if that's wanted.

Also worth noting: test:unit:lmdb itself still chains its three sub-runs (resources/apitests/bin under the LMDB engine) with &&, so a failure in the first still hides the other two — the comment above the steps calls this out. Splitting further wasn't in scope: the issue's own suite list treats lmdb as one of the four suites, matching test:unit:all's definition in package.json:74.

Verification

  • Read the workflow and package.json's test:unit:* scripts to confirm the four steps preserve test:unit:all's exact suite set and order.
  • Pulled real per-suite timing from three recent Actions run logs (gh run view --log) to size each step's timeout off measured data rather than guesswork.
  • Validated YAML with python3 -c "import yaml; yaml.safe_load(...)" and formatting with npx prettier --check.
  • No test code changed; job-level timeout-minutes: 45 unchanged.
  • Did not run a live workflow_dispatch — draft PR pushes to this branch, so the PR's own Node 22/24/26 checks are the first real-world exercise of the split; will watch those once pushed.

Refs #2587 (and harper#2017, same structural cause from the failure side).

🤖 Generated with Claude Code

https://claude.ai/code/session_01BHWm1JSfHJeQju7R7fPKyc

Review-Coverage: authored=claude; ran=gemini,cursor-composer,codex; adjudicated=domain; declined=cursor-grok; rounds=5; full=1 @ 6cc1b39

Human-Review-Need: 3 (decisions: suite-split-granularity, continue-after-suite-failure, ci-suite-list-location, cap-sizing-policy) @ 6cc1b39

The combined `npm run test:unit:all` step chained main, apitests,
resources, and lmdb behind one 15-minute timeout: a hang or timeout
in any one suite killed the step and skipped every later suite, with
no way to tell from the checks list which suite actually died.

Give each suite its own named step with its own timeout, sized off
measured Node 22 durations (the slowest leg, ~9-10m combined across
three recent runs). The three later steps run whenever Setup Harper
succeeded, regardless of an earlier suite's outcome, so one bad suite
doesn't suppress the others' results — but they still skip on
cancellation or on a setup failure, since a skipped step's outcome is
never 'success'.

Fixes harper#2587. Also addresses harper#2017 (same structural cause
from the failure side).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHWm1JSfHJeQju7R7fPKyc
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@kriszyp
kriszyp marked this pull request as ready for review September 14, 2026 16:50
@kriszyp
kriszyp merged commit a17a3ee into main Sep 14, 2026
45 checks passed
@kriszyp
kriszyp deleted the ci/split-unit-test-steps branch September 14, 2026 16:50
@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

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