Repository navigation
feat(tooling): tracked flaky-test quarantine (cli, vitest-wired) #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
heavygee
wants to merge
4
commits into
main
Choose a base branch
from
feat/flaky-quarantine
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
6dbecda
feat(tooling): tracked flaky-test quarantine (cli, vitest-wired)
heavygee 0d6b4c7
fix(tooling): close cold-review findings on flaky-quarantine
heavygee 26d4180
fix(tooling): address fork bot findings on flaky-quarantine (P1 x2, P…
heavygee 0ef5325
fix(tooling): close round-2 fork bot findings on flaky-quarantine (P1…
heavygee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| # Flaky-test quarantine registry — tracked, not ignored. | ||
| # | ||
| # FORK-ONLY DATA: these entries are fork-specific CI observations and issue | ||
| # links (heavygee/hapi). The mechanism that reads this file | ||
| # (scripts/tooling/flaky-quarantine.mjs + lib/flaky-quarantine-core.mjs) is | ||
| # generic — any repo with CI would want it — and is a candidate for an | ||
| # eventual standalone/upstream extraction. Keep the split: this file carries | ||
| # the fork's observations, not the other way around. | ||
| # | ||
| # WHY THIS EXISTS (2026-10-15): operator-directed after two unrelated pieces | ||
| # of work were held hostage by red lights nobody could fix locally — PR #222 | ||
| # (iOS app-build failed 3 different ways on a diff that never touches the | ||
| # failing screens, heavygee/hapi#273) and the #270 idle-gate remat (tripped | ||
| # by agentCliGuard.test.ts under full-suite timing, heavygee/hapi#274). Each | ||
| # time, an agent escalated to the operator for a judgement call on a test it | ||
| # didn't write. "Red doesn't really mean red" is the disease this fixes. | ||
| # | ||
| # Evidence bar — do not add an entry without ONE of: | ||
| # - "isolation passes, full-suite fails" (both runs actually observed, not | ||
| # asserted — log what you saw in the linked issue) | ||
| # - ">=2 failures with DIFFERENT assertions/signatures on the same | ||
| # unchanged commit" (same bug retried is not flake; this is a regression) | ||
| # "Failed once" is never sufficient evidence. | ||
| # | ||
| # Checked by: scripts/tooling/flaky-quarantine.mjs (validate|check), called | ||
| # from both CI (.github/workflows/test.yml, ios.yml) and the local remat gate | ||
| # (hapi-driver-rebuild.sh) via scripts/tooling/flaky-quarantine-gate.sh — ONE | ||
| # script, so the two gates cannot drift onto different lists. | ||
| # | ||
| # Fields (all required unless noted): | ||
| # id - stable test identifier. | ||
| # vitest: "<package>:<ancestorTitles-space-joined> <title>" | ||
| # e.g. produced verbatim by vitest's own | ||
| # --reporter=json `fullName` field — see | ||
| # `flaky-quarantine.mjs from-vitest-json`. | ||
| # XCTest: "ios:<TestClass>/<testMethod>" | ||
| # owner - person/agent handle responsible for eventually fixing or | ||
| # removing this entry. No anonymous entries. | ||
| # issue - heavygee/hapi issue number tracking the root cause. MUST | ||
| # be OPEN (validated against the live GitHub issue state, | ||
| # not just present) — a quarantined test whose issue is | ||
| # closed is either fixed or abandoned, and both deserve to | ||
| # fail loudly again, not sit here forever. | ||
| # signature - one or more " && "-joined required substrings of the | ||
| # known failure text — ALL must be present in the message | ||
| # for a match. A failure under this id that doesn't match | ||
| # is treated as a NEW failure and BLOCKS — this is what | ||
| # stops quarantine from hiding a real regression that | ||
| # happens to land in the same test. Prefer combining the | ||
| # assertion text with its stack-trace call site (e.g. | ||
| # "expected true to be false && myTest.ts:42:") — either | ||
| # alone can over-match (generic assertion text can hit a | ||
| # different assertion in the same test; a bare call site | ||
| # can match a different kind of failure thrown from the | ||
| # same line). See the cli: entry below for the real case | ||
| # that forced this (cold review, 2026-10-15). | ||
| # added - ISO date quarantined. | ||
| # expires - ISO date. Default policy is +14 days from `added`. Past | ||
| # this date the entry excuses nothing — it is not a grace | ||
| # period, the entry simply drops out of the active set and | ||
| # the test goes back to blocking until fixed, deleted, or | ||
| # deliberately renewed. | ||
| # renewal_count - (optional, default 0) each +1 permits 14 more days | ||
| # beyond the default (enforced by the validator: expires | ||
| # may not be more than 14 + 14*renewal_count days after | ||
| # added). You cannot push the date out without bumping | ||
| # this — the renewal itself is a diff. | ||
| # renewal_reason - required whenever renewal_count > 0. A concrete reason | ||
| # ("renewed: still intermittent under CI load, see #274"), | ||
| # not a bare date bump. Older renewal reasons live in git | ||
| # log on this file, not here — this field is the latest. | ||
| # | ||
| # Ceiling: scripts/tooling/flaky-quarantine.mjs defaults to 8 active entries. | ||
| # Past that, the gate fails regardless of test outcomes until the registry is | ||
| # cleaned up — the count is a forcing function, not a decoration. | ||
|
|
||
| quarantine: | ||
| - id: "cli:agentCliGuard prunes crash-stale registering/<deadPid> so list-models is not pinned" | ||
| owner: "heavygee" | ||
| issue: 274 | ||
| # Two cold-review rounds landed here (2026-10-15): | ||
| # Round 1: "expected true to be false" alone is too generic — this test | ||
| # has a SECOND assertion two lines below (existsSync(dir)).toBe(false)) | ||
| # that fails with byte-identical text for a real, different reason. | ||
| # Round 2: the stack-frame line ALONE is also not enough — if | ||
| # isAgentAcpTransportActive() itself started throwing instead of | ||
| # returning the wrong boolean, the thrown error's stack would still | ||
| # name this same line, and a location-only signature would wrongly | ||
| # excuse a completely different failure mode. | ||
| # `signature` supports " && "-joined required substrings — ALL must be | ||
| # present. Combining the assertion text with its call site closes both | ||
| # gaps: neither alone can satisfy this quarantine's excuse condition. | ||
| # Line-number signatures are brittle to lines being inserted above 309 in | ||
| # this file — if that happens, this stops matching and BLOCKS (fails | ||
| # closed), which forces someone to look rather than silently re-matching. | ||
| signature: "expected true to be false && agentCliGuard.test.ts:309:" | ||
| added: "2026-10-15" | ||
| expires: "2026-10-29" | ||
|
|
||
| # NOT WIRED YET (see docs/tooling/flaky-quarantine.md — the ios adapter is | ||
| # built and proven against real logs, but has no macOS runner to verify the | ||
| # live ios.yml integration). Known limitation carried over from the cold | ||
| # review that caught the cli entry's generic-signature problem above: these | ||
| # three ios signatures are the most specific substring captured from the | ||
| # real failing run WITHOUT including per-run-variable text (memory | ||
| # addresses, measured pixel values). They have NOT been checked against the | ||
| # iOS test source for a sibling assertion in the same method that could | ||
| # produce the same generic text — do that check before wiring these live. | ||
| - id: "ios:ToolTranscriptPresentationTests/testPlanPublicationUpdatesRecyclingAndInspectorPreserveTheDocumentAndAnchor" | ||
| owner: "heavygee" | ||
| issue: 273 | ||
| signature: "XCTAssertNil failed: \"<UICollectionViewCell" | ||
| added: "2026-10-15" | ||
| expires: "2026-10-29" | ||
|
|
||
| - id: "ios:HomeFilterPresentationTests/testFilteringResetsScrollButLiveUpdatesAndChatReturnPreserveIt" | ||
| owner: "heavygee" | ||
| issue: 273 | ||
| signature: "XCTUnwrap failed: expected non-nil value of type \"UICollectionView\"" | ||
| added: "2026-10-15" | ||
| expires: "2026-10-29" | ||
|
|
||
| - id: "ios:TranscriptTypographyTests/testShrinkingTextClampsAnOffsetBeyondTheNewRowHeight" | ||
| owner: "heavygee" | ||
| issue: 273 | ||
| signature: "XCTAssertEqualWithAccuracy failed" | ||
| added: "2026-10-15" | ||
| expires: "2026-10-29" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| # Flaky-test quarantine | ||
|
|
||
| ## Why this exists | ||
|
|
||
| Two unrelated pieces of work were held hostage by red lights nobody could fix | ||
| locally (2026-10-15): [PR #222](https://github.com/heavygee/hapi/pull/222) | ||
| blocked on iOS `app-build` failing 3 different ways on a diff that never | ||
| touches the failing screens ([#273](https://github.com/heavygee/hapi/issues/273)), | ||
| and the #270 idle-gate remat tripped by `agentCliGuard.test.ts` under | ||
| full-suite timing ([#274](https://github.com/heavygee/hapi/issues/274)). Each | ||
| time, an agent escalated to the operator for a judgement call on a test it | ||
| didn't write. A tolerated red light teaches everyone that red doesn't really | ||
| mean red — this is the fix. | ||
|
|
||
| **A quarantined test still runs and still reports.** This is a tracked | ||
| quarantine, not an ignore list: every entry has an owner, an open issue, a | ||
| captured failure signature, and an expiry. Nothing here hides a test from CI | ||
| output; it only decides whether a *known* failure may be excused for | ||
| gate-pass purposes. | ||
|
|
||
| ## The three rules | ||
|
|
||
| 1. **Known id + matching failure signature → excused (non-blocking).** | ||
| 2. **Known id + a DIFFERENT signature → BLOCKS.** Id-matching alone can't | ||
| tell "the same old flake" from "a new bug that landed in the same test" — | ||
| comparing the failure text closes most of that gap. A warning nobody must | ||
| act on is exactly the mechanism that trained everyone to ignore red in the | ||
| first place, so a mismatch blocks rather than warns. | ||
| 3. **An expired entry excuses nothing.** It is not a grace period — the | ||
| entry drops out of the active set, and the next failure under that id is | ||
| an ordinary blocking failure until someone renews (with a reason) or | ||
| removes it. | ||
|
|
||
| Plus a **ceiling** (default 8) on the number of active entries: past it, the | ||
| gate fails regardless of whether today's tests pass, until the registry is | ||
| cleaned up. The count is a forcing function, not a decoration. | ||
|
|
||
| ## Where the data lives | ||
|
|
||
| `config/flaky-quarantine.yaml` — one file, read by both consumers (the local | ||
| remat gate and CI), so they cannot drift onto different lists. Field | ||
| reference and evidence bar are documented in the file's own header comment. | ||
|
|
||
| **Fork-only data; generic mechanism.** The YAML's content (which tests, which | ||
| issues, which owners) is specific to `heavygee/hapi`'s own CI observations. | ||
| The code that reads it (`scripts/tooling/flaky-quarantine.mjs` + | ||
| `scripts/tooling/lib/flaky-quarantine-core.mjs`) is generic — any repo with | ||
| CI would want it — and is a candidate for an eventual standalone/upstream | ||
| extraction. Don't let fork-specific assumptions leak into the script. | ||
|
|
||
| ## How it's wired | ||
|
|
||
| ``` | ||
| config/flaky-quarantine.yaml | ||
| / \ | ||
| scripts/tooling/flaky-quarantine-gate.sh ios/scripts/test-transcript.sh | ||
| (hapi-driver-rebuild.sh --verify, (ios.yml — DRAFTED, not yet | ||
| .github/workflows/test.yml) applied; see below) | ||
| ``` | ||
|
|
||
| - **cli (vitest):** `flaky-quarantine-gate.sh <repo-root>` runs hub/web/ | ||
| shared/relay exactly as before (unchanged, fully blocking), then runs | ||
| `cli`'s tests via `vitest run --reporter=default --reporter=json | ||
| --outputFile.json=...`, converts the JSON report | ||
| (`flaky-quarantine.mjs from-vitest-json`) and feeds it to | ||
| `flaky-quarantine.mjs check`. Same script, called from both | ||
| `hapi-driver-rebuild.sh`'s `--verify` test step and | ||
| `.github/workflows/test.yml`'s `test` job (fork-only — upstream and other | ||
| forks keep plain `bun run test`, see the `github.repository_owner` guard in | ||
| that file). | ||
| - **iOS (XCTest):** `flaky-quarantine.mjs from-xcodebuild-log` parses | ||
| xcodebuild's `error: -[Class method] : <message>` failure lines into the | ||
| same normalized `{id, failed, message}` shape, proven against the three | ||
| real PR #222 attempt logs (`ios:<TestClass>/<testMethod>` ids). **Not yet | ||
| wired into `ios/scripts/test-transcript.sh` / `ios.yml`** — this host has | ||
| no macOS runner, so the actual shell integration (capturing xcodebuild's | ||
| stdout, `bun` on the GitHub-hosted `macos-15` runner, which `ios.yml` | ||
| doesn't currently set up at all) could not be executed or verified here. | ||
| Wiring it blind into a live gating workflow risked turning a flake into a | ||
| hard break. Apply + smoke-test on an actual macOS run before trusting it. | ||
|
|
||
| ## Validating the registry | ||
|
|
||
| ```bash | ||
| bun scripts/tooling/flaky-quarantine.mjs validate | ||
| ``` | ||
|
|
||
| Checks schema (owner/issue/signature/dates present and sane, renewal math | ||
| consistent with `renewal_count`), confirms every `issue` is still OPEN on | ||
| GitHub, and reports the active count against the ceiling. Exit 2 on any | ||
| failure — safe to run standalone in a lint step. | ||
|
|
||
| ## Negative controls | ||
|
|
||
| `scripts/tooling/flaky-quarantine.test.sh` (picked up automatically by the | ||
| existing `tooling-tests` CI job, no workflow change needed) proves, with | ||
| synthetic fixtures, that the gate can actually go red three ways before | ||
| anyone trusts it to go green: a quarantined test failing with its known | ||
| signature does not block; the same id failing with a different signature | ||
| blocks; an expired entry blocks even with a matching signature. Plus schema | ||
| and ceiling checks. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| #!/usr/bin/env bash | ||
| # flaky-quarantine-gate.sh <repo-root> | ||
| # | ||
| # Shared `bun run test` replacement for BOTH the local remat gate | ||
| # (hapi-driver-rebuild.sh) and CI (.github/workflows/test.yml) — the SAME | ||
| # script, so the two gates cannot drift onto different interpretations of | ||
| # config/flaky-quarantine.yaml. See docs/tooling/flaky-quarantine.md. | ||
| # | ||
| # Only `cli` is quarantine-aware today — that is where the two seed entries | ||
| # (heavygee/hapi#273's iOS siblings are a separate `ios:` id space checked by | ||
| # ios/scripts/test-transcript.sh; #274 is the cli-side entry here) live. | ||
| # hub/web/shared/relay run exactly as before, unchanged, fully blocking. | ||
| # Extending quarantine awareness to another package is the same | ||
| # vitest-json-reporter + adapter + check sequence repeated for that package. | ||
| set -euo pipefail | ||
|
|
||
| REPO_ROOT="${1:?usage: flaky-quarantine-gate.sh <repo-root>}" | ||
| HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| # Honor an inherited $BUN (hapi-driver-rebuild.sh resolves "${BUN:-$HOME/.bun/bin/bun}" | ||
| # but does not export it — callers must pass it through explicitly, e.g. | ||
| # `BUN="$BUN" bash flaky-quarantine-gate.sh ...`). Falling back to bare `bun` | ||
| # only works when it happens to be on PATH, which the caller's own fallback | ||
| # does not guarantee (cold review, 2026-10-15). | ||
| BUN="${BUN:-bun}" | ||
|
|
||
| run_plain() { | ||
| echo "Running $1 tests..." | ||
| (cd "$REPO_ROOT/$1" && "$BUN" run test) | ||
| } | ||
|
heavygee marked this conversation as resolved.
|
||
|
|
||
| run_plain hub | ||
| run_plain web | ||
| run_plain shared | ||
| run_plain relay | ||
|
|
||
| # Whole-registry health (ceiling + schema + issue-open-ness) is its OWN gate, | ||
| # run once, clearly attributed — NOT folded into the package-scoped `check` | ||
| # below. Without this split, an unrelated package's entry going bad (e.g. an | ||
| # `ios:` issue getting closed) would fail THIS cli-only run for a reason that | ||
| # has nothing to do with cli, which is exactly the cross-package coupling the | ||
| # --package flag on `check` exists to avoid. The ceiling itself is | ||
| # deliberately whole-registry (not per-package) — see flaky-quarantine.mjs. | ||
| echo "Validating quarantine registry (whole-file: ceiling + schema + issue-open)..." | ||
| if ! "$BUN" "$HERE/flaky-quarantine.mjs" validate --registry "$REPO_ROOT/config/flaky-quarantine.yaml"; then | ||
| echo "ERROR: config/flaky-quarantine.yaml itself is invalid or over ceiling — fix the registry before any gate can trust it." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Running cli tests (quarantine-aware)..." | ||
| CLI_JSON="$(mktemp -t flaky-quarantine-cli-XXXXXX.json)" | ||
| cleanup() { rm -f "$CLI_JSON" "${NORMALIZED:-}"; } | ||
| trap cleanup EXIT | ||
|
|
||
| set +e | ||
| (cd "$REPO_ROOT/cli" && "$BUN" run tools:unpack && "$BUN" x vitest run --reporter=default --reporter=json --outputFile.json="$CLI_JSON") | ||
| VITEST_RC=$? | ||
| set -e | ||
|
heavygee marked this conversation as resolved.
|
||
|
|
||
| if [[ ! -s "$CLI_JSON" ]]; then | ||
| echo "ERROR: cli test run produced no JSON report (a setup step likely failed before vitest ran, exit $VITEST_RC) — hard failure, not quarantine-eligible" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| NORMALIZED="$(mktemp -t flaky-quarantine-results-XXXXXX.json)" | ||
| "$BUN" "$HERE/flaky-quarantine.mjs" from-vitest-json "$CLI_JSON" --package cli >"$NORMALIZED" | ||
| # --runner-exit-code: a JSON report CAN exist (vitest wrote it) while vitest | ||
| # itself still exited non-zero with zero attributable per-test failures — a | ||
| # collection/import error or zero tests discovered. Without passing this | ||
| # through, that failure mode would silently normalize to an empty results | ||
| # list and the gate would report a clean pass (cold review, 2026-10-15). | ||
| "$BUN" "$HERE/flaky-quarantine.mjs" check --results "$NORMALIZED" --package cli --registry "$REPO_ROOT/config/flaky-quarantine.yaml" --runner-exit-code "$VITEST_RC" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.