Skip to content

fix(ci): read the test shard jobs in the Bun crash census and fix stale CI test job docs - #476

Merged
roodboi merged 1 commit into
nextfrom
wf/g89-ci-sharding-followups
Oct 24, 2026
Merged

roodboi merged 1 commit into
nextfrom
wf/g89-ci-sharding-followups

Conversation

@roodboi

@roodboi roodboi commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Follow-ups to #442 (CI sharding, merged as 6e4bcf2b). Parts (1) and (2) are fixed here. Part (3), the release workflow, is a proposed diff only: it is not part of this PR. The reason is in that section below.

Root cause

Since #442, the hosted suite runs in four test shard i/4 jobs. The test job is now only the required fan-in. It needs test-shard and runs no Bun.

  1. Two docs still said a test "runs in the CI test job".
  2. bun run ci:bun-crash-census defaulted to --job test, so it read the fan-in. A failed fan-in log has no Bun output, so the census reported zero crashes for it and gave no warning. workflowJobBunVersion(ci.yml, "test") found the fan-in's block, which has no bun-version, and "test shard 1/4" matched no workflow key at all. The workaround in .ai/skills/hack-repo-verify/SKILL.md (--job "test shard i/4") did not work either. The --job check refused /, so the census exited with Invalid argument (reproduced on origin/next).
  3. .github/workflows/release-prepare.yml still runs the serial bun run turbo:test. It has no timeout-minutes, so GitHub's 360 min default applies, and Turbo buffers the output.

Fix

(1) Docs. docs/security/env-secrets-threat-model.md:99 and docs/reference/shared-host-lifecycle.md:230 now say the test runs in one of the hosted test shard i/4 jobs, and that the required test check passes only when every shard passes.

(2) Crash census (scripts/ci-bun-crash-census.ts):

  • Default jobs: with no --job, the census reads every test shard i/n job of a run. A run with no shard job is from before the split, so its test job ran the suite and is read instead. The same window can span both kinds of run.
  • --job values: --job now accepts / and a * pattern. --job "test shard 1/4" and --job "test shard *" both work. Other characters are still refused.
  • Workflow Bun version: workflowJobBunVersion now matches a job by its name: template, read only under jobs:. A ${{ ... }} expression in the template matches any text. So test shard 2/4 finds the test-shard matrix job's pin, and test still returns null.
  • Warnings: the report adds a closed warning: line when no job matched the selector, or when a selected job shows no Bun setup in its log or workflow block. The same line goes to stderr, and --json gets a warnings array. The warning contains no log content.
  • Docs: .ai/skills/hack-repo-verify/SKILL.md now describes the new default.

Tests and evidence

Focused tests: tests/ci-bun-crash-census.test.ts (16 pass). New cases:

  • Default selection on a mixed window: shard jobs are read, the fan-in and docker-e2e shard jobs are skipped, and a run from before the split is still read.
  • Explicit --job test on a sharded run warns, and the fan-in log content is not printed.
  • A selector that matches no job warns.
  • The name-template lookup, including quoted templates and on: keys.
  • A contract check against the real .github/workflows/ci.yml: all four shard names resolve to the package.json packageManager Bun pin, and test resolves to none.
  • --job with / and * is accepted; ; and values over 100 characters are refused.

Mutation proof: each of these 8 mutations makes the suite fail:

  • the default selector reads test;
  • the name template is ignored;
  • no warnings are returned;
  • the no_bun_setup warning is dropped;
  • the old --job check is restored;
  • the workflow version is looked up under the selector name instead of the job's name;
  • the default job is test again;
  • warnings are not rendered.

Live read-only census (GitHub API GET requests only; window 2026-10-24 05:00Z to 12:00Z, 17 CI runs):

Version Selector Jobs Result
origin/next default (test) 2 One is the fan-in with bun=unknown; no warning.
This branch default 5 Every job has a Bun version (log:1,workflow:4,unknown:0); no warning.
This branch --job test 2 The fan-in prints a warning: 1 of 2 selected jobs show no Bun setup ... line.
This branch --job "test shard 1/4" 1 Runs; this value was refused before.

Other checks:

  • Root bun run check passes, including privacy:check.
  • bun run --cwd packages/cli typecheck passes with 0 errors.
  • bunx ultracite check on the changed files passes.
  • Neighbouring tests pass: shared-host-lifecycle, aes-gcm-v1-context-binding, bun-crash-loop and agent-instruction-source, which reads the verify skill.
  • gitleaks 8.30.1 over origin/next..HEAD found no leaks.

Full suite: the hosted CI test check (all four shards) at this PR's head.

(3) release-prepare: proposed diff, not applied

Why it is not applied. The change cannot be validated without a release dispatch or a push to main, and both are outside this unit. release-prepare.yml runs only on push: main and workflow_dispatch. A pull request never runs it, so no hosted check in this PR would exercise the change. Two findings make a run-free change unsafe to claim:

  • next's release-prepare cannot pass as written. On next, turbo:test is bun run test:preflight && turbo run test. The preflight needs the matching native config compiler (dist/hack-config-compiler). release-prepare.yml installs no Rust toolchain and never runs bun run build:config-compiler, so the preflight fails before any test starts. On main, turbo:test has no preflight. The last main release-prepare (40050242500) ran 165 files in 30 s. next has 579 files, planned at about 42 min on the macOS shard runner.
  • No hosted job runs next's full suite on Linux. The test shard jobs run on blacksmith-6vcpu-macos-15, and linux-process-lifetime runs only 6 files. release-prepare runs on blacksmith-4vcpu-ubuntu-2404. So the Linux duration and the Linux pass/fail result of the whole suite are both unmeasured.

The change only matters when next is merged into main. The workflow that runs is the copy on main, and scripts/test-shard.ts exists only on next, so this diff must not be applied to main by itself. It applies cleanly to next at 6e4bcf2b:

--- a/.github/workflows/release-prepare.yml
+++ b/.github/workflows/release-prepare.yml
@@ -16,6 +16,9 @@
 jobs:
   semantic-release:
     runs-on: blacksmith-4vcpu-ubuntu-2404
+    # GitHub's default limit is 360 min. The suite's own 90 min deadline stops
+    # first and names the running file (HACK-1424).
+    timeout-minutes: 120
     env:
       FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
     steps:
@@ -47,8 +50,17 @@
       - name: Quality checks (Turbo)
         run: bun run turbo:check
 
-      - name: Tests (Turbo)
-        run: bun run turbo:test
+      # test:preflight needs the matching native config compiler (HACK-1424).
+      - uses: dtolnay/rust-toolchain@1.97.1
+
+      - name: Prepare matching config compiler
+        run: bun run build:config-compiler
+
+      # The `bun run test` file set, Bun pin check and compiler preflight, one
+      # `bun test` process per file with start/PASS/FAIL lines, a 12 min file
+      # limit and a 90 min deadline that names the running file (HACK-1381).
+      - name: Tests (per file)
+        run: bun scripts/test-shard.ts run --shard=1/1 --deadline-seconds=5400
 
       - name: Run semantic-release
         env:

What stays the same:

  • Gates: typecheck, privacy check, quality checks, then the tests. semantic-release runs only if every earlier step passes.
  • The test run still fails when it should. The runner exits non-zero when any file fails, times out, reports a file count other than 1, or reaches the deadline.
  • Exact-head semantics: one job, one checkout of the pushed SHA, the same concurrency group, cancel-in-progress: false.
  • Outputs: the same semantic-release step with the same token. No new outputs, artifacts or permissions.

What is new:

  • check:bun-version now runs before the tests. It already runs before every bun run test and every CI shard.
  • The git ls-files coverage check: verify --count=1 on this head reports "579 files in 1 shards cover the full test set exactly once", planned at 42m22s.
  • Per-file progress lines, a heartbeat every 2 min, a 12 min limit per file, a 90 min limit for the run, and a 120 min limit for the job.

Validated statically:

  • actionlint 1.7.12 with the repository config passes on both the current and the proposed file.
  • bun scripts/test-shard.ts verify --count=1 and plan --shard=1/1 succeed on this head.

Not validated: the run itself on the Linux release runner (compiler build, Linux suite result and duration). That needs one workflow_dispatch of release-prepare.yml against next, or the next-to-main merge. On next, a dispatched run would also reach bunx semantic-release if the tests pass, so a dry run would need --dry-run or a temporary branch filter. I did not dispatch it.

Gaps

  • (3) is proposed, not applied. HACK-1424 acceptance allows "explicitly scheduled with a reason", and the reason is above.
  • The census's live evidence covers a 7-hour window around the fix(ci): shard the hosted test suite so a slow runner cannot hit the 60 min limit #442 merge, not a long history.
  • A job whose workflow fetch fails is also counted as "no Bun setup" in the warning. This is intended: in both cases the zero is not evidence.

Refs HACK-1424

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…le CI test job docs

Since #442 the `test` job is only the required fan-in of the four
`test shard i/4` jobs. It runs no Bun, but `ci:bun-crash-census` still
read it by default and reported zero crashes without saying so, and
`workflowJobBunVersion(ci.yml, "test")` found the fan-in block, which
has no setup-bun.

- With no --job, the census reads every `test shard i/n` job of a run,
  or the `test` job of a run that has no shard job (a run from before
  the split, whose `test` job ran the suite).
- --job accepts `/` and a `*` pattern, so the documented
  `--job "test shard 1/4"` and `--job "test shard *"` work; before,
  both were refused as an invalid argument.
- The workflow lookup matches a job by its `name:` template, so
  `test shard 2/4` finds the `test-shard` matrix job's Bun pin.
- The report prints a closed `warning:` line (also on stderr, and in
  --json) when no job matched or a selected job shows no Bun setup in
  its log or workflow block.
- docs/security/env-secrets-threat-model.md and
  docs/reference/shared-host-lifecycle.md now say the tests run in a
  `test shard i/4` job and that `test` is the fan-in.

Refs HACK-1424

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 24, 2026

Copy link
Copy Markdown

HACK-1424

@roodboi
roodboi marked this pull request as ready for review October 24, 2026 18:06
@roodboi
roodboi merged commit 101d263 into next Oct 24, 2026
21 checks passed
@roodboi
roodboi deleted the wf/g89-ci-sharding-followups branch October 24, 2026 18:06
roodboi added a commit that referenced this pull request Oct 24, 2026
Resolve the conflict with #476's squash (101d263) by keeping the PR side
of scripts/ci-bun-crash-census.ts and tests/ci-bun-crash-census.test.ts.
The #476 content is identical on both sides; only the HACK-1441 changes differ.

Refs HACK-1441

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
roodboi added a commit that referenced this pull request Oct 24, 2026
…d skip comment lines in jobs (#489)

Refs HACK-1441

Follow-ups from the #476 verifier on `scripts/ci-bun-crash-census.ts`. **Stacked on #476:** #476
is still open, so this branch merges its head (`e3814e87`) on top of `next` (`1281184b`). Until
#476 lands, this PR's diff also shows #476's changes. Only commit `159df1e0` belongs to this PR.

## Root cause

1. **Cache key had no test.** If you change ``const cacheKey = `${sha}\n${job}` `` to `const
cacheKey = sha`, all 16 existing tests still pass. With a wildcard selector, a sha-only key
gives every job at that sha the version of whichever job resolves first. For example, the `test`
fan-in (no Bun) and a shard (pinned Bun) would share one result. 2. **Column-0 comments ended
the job scan.** `workflowJobBunVersion` stopped at the first non-indented line after `jobs:`. A
column-0 `# comment` inside `jobs:` therefore hid every later job, and the function returned
null for those jobs. 3. **One fetch per job name.** The only cache was keyed by (sha, job), so
the same `ci.yml` was fetched once for each distinct job name at a commit. That is up to 4 GETs
per sha for the shards, plus 1 for the fan-in.

## Fix

- Add a per-sha cache of the decoded workflow text. Each sha now costs one contents GET,
  whatever the number of jobs, runs or concurrent workers. The cache stores the promise, so
  concurrent runs at the same sha share the in-flight request.
- Keep the per-(sha, job) version memo on top of that cache. A comment explains why the key must
  never be the sha alone.
- `workflowJobBunVersion` now skips blank lines and lines that start with `#` in column 0. Any
  other column-0 line is still the next top-level key and still ends the jobs.
- The header doc now says the workflow is fetched once per commit.

## Tests (`tests/ci-bun-crash-census.test.ts`, new `workflow reads (HACK-1441)` block)

- **Comment and blank lines:** a workflow with a column-0 comment, blank lines, a commented-out
  job and a top-level `env:` key after `jobs:`. Shard, lint, fan-in, commented-out-job and
  post-`jobs:` lookups each return the right value.
- **Per-job version under `--job "test*"`:** three runs, two of them at the same sha. In one run
  the fan-in is listed first; in another a shard is listed first. Every shard gets `1.4.2
  workflow`, the fan-in gets `unknown`, and the expected warning is `no_bun_setup 2/9`.
- **Fetch count:** the same fixture at concurrency 1 and 3 makes exactly one contents GET per
  sha (2 in total).

## Evidence

- Focused tests: `bun test tests/ci-bun-crash-census.test.ts` gives 19 pass, 0 fail (16 existing
  + 3 new), on Bun 1.4.2.
- Mutation results. Each mutation was applied to the fixed script, tested, and then the file was
  restored:

| Mutation | Result |
  |---|---|
  | M1: `cacheKey = sha` | killed by the per-job version test |
  | M2a: column-0 comment ends the scan again | killed by the comment test and the version test
  |
  | M2b: full revert to break-only behavior | killed |
  | M2c: skip every column-0 line (never end `jobs:`) | killed by the comment test (`late`
  lookup) |
  | M3: no per-sha text cache (the pre-fix behavior) | killed by the fetch-count test |
- Gates: root `bun run check` passes (bun-version, privacy:check, turbo check; existing warnings
  only). `bun run --cwd packages/cli typecheck` has 0 errors and covers `tests/`, which imports
  the script. Biome is clean on both changed files. gitleaks 8.30.1 (`gitleaks dir` over a `git
  log -p origin/next..HEAD` export) found no leaks.
- No CLI surface or consumer agent guidance changed, so I did not run `docs:cli-reference` or
  `generate:agent-plugins`.
- Full suite: hosted CI `test` shards at this PR's exact head.

## Gaps

- No live `gh` census was run against GitHub. The behavior is proven with the fake `GhReader`
  only.
- If `#476` is squash-merged before this PR, `next` has to be merged into this branch again.
  Expect a possible conflict at the end of the test file, because both sides add lines after the
  same last block.

## Round 2: merge `next` after #476 landed

#476 was squash-merged into `next` as `101d2638`. This branch still carried #476 as the merge commit `0482673b`, so the PR conflicted with `next` (`d09e1b73`) in `scripts/ci-bun-crash-census.ts` and `tests/ci-bun-crash-census.test.ts`.

- **Fix:** merge commit `499e8019` merges `next` (`d09e1b73`) into the branch and keeps the PR
  side of both census files. Before resolving, I checked that #476's five files are byte-
  identical between `0482673b` and `d09e1b73` (`git diff` is empty), so the PR side differs from
  `next` only by the HACK-1441 changes.
- **Diff now:** `git diff next HEAD` is only the two HACK-1441 files (+177/-20). The "Stacked on
  #476" note above no longer applies, because #476's changes are already in `next`.
- **Focused tests at `499e8019`:** 19 pass, 0 fail, 87 expects (Bun 1.4.2).
- **Mutations re-run at `499e8019`:** each was applied, tested and then restored. M1 (`cacheKey
  = sha`) was killed by the per-job version test. M2 (skip block removed) was killed by the
  comment test and the per-job test. M3 (per-sha text cache never hits) was killed by the fetch-
  count test.
- **Gates at `499e8019`:** root `bun run check` exits 0 (privacy-check ok). `bun run --cwd
  packages/cli typecheck` exits 0 with 0 errors. Biome is clean on both files. gitleaks 8.30.1
  (`gitleaks dir` over a `git log -p origin/next..HEAD` export plus the net diff) found no
  leaks.
- **Hosted CI at exactly `499e8019`:** run 40120172542 passed all 20 checks, including `test`
  shards 1/4 to 4/4, `docker-e2e` shards 1/4 to 4/4, secret-scan, Candidate core, Config
  compiler (all platforms), Runtime state models, linux-process-lifetime and runtime-images.
  This replaces the earlier evidence from `159df1e0`.
- **Remaining:** after CI started, `next` gained one more commit, `78487f72` (#487). `git merge-
  tree` reports a clean merge, and that commit touches no census files. GitHub shows `BEHIND`,
  not a conflict.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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