Skill Tracer
Skills › gstack › investigate › flake.md

flake.md

74 lines · investigate/sections/flake.md at 3f79d81

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1<!-- AUTO-GENERATED from flake.md.tmpl — do not edit directly -->
2<!-- Regenerate: bun run gen:skill-docs -->

<!-- AUTO-GENERATED from flake.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->

Raw lines 1 to 2
3## Flake recipe
4
5A flake is a test whose verdict depends on something other than the code it
6guards. The Iron Law holds here: no rerun, no wider timeout, no retry and no
7skip until the cause is named and probed.
8
91. **Reproduce on main.** Check out `origin/<base>` in a worktree
10 (`git worktree add <dir> origin/<base>`) and run the failing file alone N
11 times (N ≥ 10). On gstack:
12 `bun --no-env-file --config=/dev/null run scripts/ship-measure.ts free --files <file> --reruns N`; elsewhere a
13 loop over the project's single-file test command. Record `failed/N` on main,
14 then the same on the branch. A failure that reproduces on main is outside the
15 PR; one that appears only on the branch is inside it.
162. **Cause inside or outside the PR.** Diff the two runs' logs at the first
17 divergence and name the dependency: timing, file order, shared state, network,
18 clock, a resource limit. "Flaky" is a symptom, never a cause.
193. **Compare the metric with the last N green runs.** Once for `<base>` and once
20 for the branch (`--branch <branch-name>`), against the workflow that ran the
21 failing shard; read the duration or retry token next to the test:
22 ```bash
23 gh run list --branch <base> --workflow free-tests.yml --status success --limit 20 --json databaseId,headSha,createdAt,updatedAt
24 gh run view <run-id> --log | grep -F -- "<failing-test-file>"
25 ```
26 The run where the duration, retry count or failing test first moved dates the
27 regression; cite its `headSha`.
284. **Forced probe.** Build the smallest change that makes the test fail
29 deterministically for the stated cause (inject the delay, pin the file order,
30 freeze the clock, exhaust the resource). Show the probe output failing before
31 the fix and passing after. No probe, no cause.
325. **Never loosen.** A wider timeout, a retry, a looser assertion or a skip is a
33 gate relaxation (review tag RH-15); the fix lives in the code or in the test's
34 isolation, not in its threshold.
35 **The flake-fix number.** A fix that touches only the test (a wider timeout, a retry,
36a looser assertion, a skip) must state `P(fail | regression)`: the probability the test
37still fails when the behavior it guards regresses, before and after the change. Derive
38it from the forced probe (break the behavior on purpose, run the test N times, count
39failures) and write both numbers in `## Flake evidence`. A number that drops is a gate
40relaxation (review tag RH-15) and needs the owner's decision, not a merge.
416. **A rerun only when the cause is known and outside the PR** (infrastructure,
42 a polluter already fixed on main, a provider outage), stated in the PR body.
43 Reruns follow `ship-measure`'s discipline: bounded, counted, logged,
44 `flaky retry off`; a free rerun is diagnostic, never the green. Paid evals
45 are never retried (`EVAL_POLICY` in `test/helpers/periodic-exclude-data.ts`).
467. **`## Flake evidence` in the PR body (required for any flake fix).** The
47 probe's before and after output, `failed/N` on main and on the branch, the
48 `gh run list` comparison with the first-moved `headSha`, and a routing note
49 for the owner: `fix in this PR` | `rerun; cause outside the PR: <what>` |
50 `owner decision needed: <the relaxation and its P(fail | regression) before/after>`.

Flake recipe

A flake is a test whose verdict depends on something other than the code it guards. The Iron Law holds here: no rerun, no wider timeout, no retry and no skip until the cause is named and probed.

  1. Reproduce on main. Check out origin/<base> in a worktree (git worktree add <dir> origin/<base>) and run the failing file alone N times (N ≥ 10). On gstack: bun --no-env-file --config=/dev/null run scripts/ship-measure.ts free --files <file> --reruns N; elsewhere a loop over the project's single-file test command. Record failed/N on main, then the same on the branch. A failure that reproduces on main is outside the PR; one that appears only on the branch is inside it.
  2. Cause inside or outside the PR. Diff the two runs' logs at the first divergence and name the dependency: timing, file order, shared state, network, clock, a resource limit. "Flaky" is a symptom, never a cause.
  3. Compare the metric with the last N green runs. Once for <base> and once for the branch (--branch <branch-name>), against the workflow that ran the failing shard; read the duration or retry token next to the test: ``bash gh run list --branch <base> --workflow free-tests.yml --status success --limit 20 --json databaseId,headSha,createdAt,updatedAt gh run view <run-id> --log | grep -F -- "<failing-test-file>" ` The run where the duration, retry count or failing test first moved dates the regression; cite its headSha`.
  4. Forced probe. Build the smallest change that makes the test fail deterministically for the stated cause (inject the delay, pin the file order, freeze the clock, exhaust the resource). Show the probe output failing before the fix and passing after. No probe, no cause.
  5. Never loosen. A wider timeout, a retry, a looser assertion or a skip is a gate relaxation (review tag RH-15); the fix lives in the code or in the test's isolation, not in its threshold. The flake-fix number. A fix that touches only the test (a wider timeout, a retry,

a looser assertion, a skip) must state P(fail | regression): the probability the test still fails when the behavior it guards regresses, before and after the change. Derive it from the forced probe (break the behavior on purpose, run the test N times, count failures) and write both numbers in ## Flake evidence. A number that drops is a gate relaxation (review tag RH-15) and needs the owner's decision, not a merge.

  1. A rerun only when the cause is known and outside the PR (infrastructure, a polluter already fixed on main, a provider outage), stated in the PR body. Reruns follow ship-measure's discipline: bounded, counted, logged, flaky retry off; a free rerun is diagnostic, never the green. Paid evals are never retried (EVAL_POLICY in test/helpers/periodic-exclude-data.ts).
  2. **## Flake evidence in the PR body (required for any flake fix).** The probe's before and after output, failed/N on main and on the branch, the gh run list comparison with the first-moved headSha, and a routing note for the owner: fix in this PR | rerun; cause outside the PR: <what> | owner decision needed: <the relaxation and its P(fail | regression) before/after>.
Raw lines 3 to 51
Runs code scripts/ship-measure.ts :12
52### Timing flakes (`TIMING_ASSERTION`)
54When the failing line compares an elapsed time with a bound or sleeps and then
55asserts (`gstack-pregate`'s `timing` check prints it as
56`TIMING_ASSERTION: <file>:<line> <pattern>`):
581. **Same-load distributions.** Log the asserted value on every run and run the
59 file N ≥ 20 times on `origin/<base>` and on the PR head under the same load
60 (same machine, same concurrency, beside the same shard). Report min, p50,
61 p95, max and failures against the bound for both. A distribution that moved
62 on the PR head is a regression in the PR; the same distribution on both is a
63 test that depends on machine load.
642. **Forced probe.** Inject the delay the load causes (a sleep inside the
65 measured span behind a test-only hook, or a starved CPU) and show the
66 assertion failing every time.
673. **Fix the measurement, never the bound.** Assert on the outcome, an injected
68 clock, a forced deadline or a poll with a deadline instead of a fixed sleep;
69 a complexity bound measures thread CPU time against a same-run baseline.
70 When a bound stays the contract, the line carries
71 `gstack-timing-ok: <reason>`. Raising the bound is not a fix.
724. **Rerun only with outside-cause evidence:** the same-load distributions show
73 no PR regression and the failed run's load is on record (CI log, runner
74 metrics). Put both in `## Flake evidence`.

Timing flakes (TIMING_ASSERTION)

When the failing line compares an elapsed time with a bound or sleeps and then asserts (gstack-pregate's timing check prints it as TIMING_ASSERTION: <file>:<line> <pattern>):

  1. Same-load distributions. Log the asserted value on every run and run the file N ≥ 20 times on origin/<base> and on the PR head under the same load (same machine, same concurrency, beside the same shard). Report min, p50, p95, max and failures against the bound for both. A distribution that moved on the PR head is a regression in the PR; the same distribution on both is a test that depends on machine load.
  2. Forced probe. Inject the delay the load causes (a sleep inside the measured span behind a test-only hook, or a starved CPU) and show the assertion failing every time.
  3. Fix the measurement, never the bound. Assert on the outcome, an injected clock, a forced deadline or a poll with a deadline instead of a fixed sleep; a complexity bound measures thread CPU time against a same-run baseline. When a bound stays the contract, the line carries gstack-timing-ok: <reason>. Raising the bound is not a fix.
  4. Rerun only with outside-cause evidence: the same-load distributions show no PR regression and the failed run's load is on record (CI log, runner metrics). Put both in ## Flake evidence.
Raw lines 52 to 74