Flaky test finder
Flakiness is an empirical claim, and it is the one kind of test problem where reasoning from the source is actively misleading. A test that looks obviously order-dependent may be solid; a test that looks pure may fail one run in forty on a loaded machine.
So this skill measures first and reads second.
Procedure
1. Measure the rate
<skill-dir> is the directory this SKILL.md was loaded from — the skill installs outside
your project, so its script is named by full path, never relatively.
bash <skill-dir>/scripts/rerun.sh "pnpm vitest run path/to/file.test.ts" 30Runs the command N times, reports pass/fail per run, the observed failure rate, and a Wilson confidence interval for the true rate. The interval is the point: three failures in ten runs and three in a hundred are very different findings, and a bare percentage hides which one you have.
If the failure rate is 0 over a decent number of runs, say so plainly — "not reproduced in N runs" is a real result. Do not go on to diagnose a flake you could not observe.
2. Split ordering from isolation
The two commonest causes look identical from the failure message. Distinguish them:
- Run the single test alone, many times. Failing alone means it is not
order-dependent — look at timing, environment and external state.
- Run the whole file, then the whole suite. If it only fails in the larger set, it is
shared state or ordering — something before it leaves a mutation behind.
- If the runner supports a random seed, vary it. A failure rate that moves with the seed is
ordering by definition.
3. Find the shared thing
When it is ordering or isolation, the cause is almost always one of a short list, and it is worth checking them in order rather than reading the test top to bottom:
- module-level mutable state, a cache, or a singleton that survives between tests
- a fake clock, timezone or locale set by one test and not restored
- database or filesystem state without a per-test transaction or temp directory
- an unawaited promise from an earlier test landing during this one
- a shared port, fixture file, or environment variable
4. Prove the fix by re-measuring
Apply the fix, then run step 1 again with at least as many iterations. The claim "fixed" is only supported by a second measurement with a comparable interval. A single green run proves nothing about a test that failed one time in twenty.
5. If it cannot be fixed now, quarantine deliberately
Say so explicitly, with the rate, and prefer an annotation the runner reports over a silent skip. A quarantined test that nobody can see becomes a deleted test.
Output
- Rate — failures over runs, with the interval, and the exact command measured
- Class — ordering, isolation, timing, environment, or not reproduced
- Evidence — which of the step-2 runs differed, and how
- Cause — the specific shared thing, with the line
- Post-fix rate — the second measurement, or an explicit "not yet re-measured"
What this skill deliberately does not do
- It does not diagnose from the source without measuring. That is the failure mode
this skill exists to replace.
- It does not add a retry. Retries hide the rate and convert a known flake into an
unknown one. If a retry is genuinely the right call, that is the author's decision to make explicitly, not a fix to apply quietly.
- It does not delete or
skipa test to make a suite green. - It does not claim a fix from one passing run.
- It does not run suites that mutate shared infrastructure — a test command touching a
shared database, a live API or a deployment is not re-run thirty times. It says why and asks for an isolated target.
- It does not report a rate without saying how many runs produced it.
When this is the wrong tool
- The test fails every single time. That is not flakiness, it is a bug with a
reproduction already in hand — diagnose it from the failure instead. Measuring a 100% rate tells you nothing you did not already know.
- The test does not exist yet and you are deciding what to cover. That is a coverage
question, not a reliability one.
- The failures started immediately after a dependency bump. Check the bump first: a
changed default explains a whole class of new intermittency.
Supporting files
scripts/rerun.shShell
#!/usr/bin/env bash
# Measure a command's failure rate by running it repeatedly.
#
# bash rerun.sh "<command>" [runs] [--stop-after N]
#
# Prints a pass/fail strip, the observed rate, and a Wilson score interval — the
# interval matters because 3/10 and 30/100 are the same percentage and very
# different evidence. Exits 0 when the command never failed, 1 when it failed at
# least once, and 2 on bad usage.
#
# Deliberately dumb about what it runs: it never inspects, retries or repairs the
# command, and it captures each run's output so a failing run can be read rather
# than re-guessed.
set -uo pipefail
CMD="${1:-}"
RUNS="${2:-20}"
STOP_AFTER=0
if [ "${3:-}" = "--stop-after" ]; then
STOP_AFTER="${4:-0}"
fi
if [ -z "$CMD" ]; then
echo "usage: rerun.sh \"<command>\" [runs] [--stop-after N]" >&2
exit 2
fi
# Digits-only, then non-zero. The digit test alone accepts `0`, which the message
# already calls invalid: zero runs skips the loop and then divides by `runs` in
# the Wilson block, so the script died on a ZeroDivisionError *and still exited
# 0* — a caller reads that as a successful measurement with no interval.
case "$RUNS" in
''|*[!0-9]*) echo "runs must be a positive integer" >&2; exit 2 ;;
esac
if [ "$RUNS" -lt 1 ]; then
echo "runs must be a positive integer" >&2; exit 2
fi
# Same guard for --stop-after: it is compared with `-gt` on every iteration, so a
# non-numeric value prints a bash error per run while the measurement continues.
case "$STOP_AFTER" in
''|*[!0-9]*) echo "--stop-after takes a non-negative integer" >&2; exit 2 ;;
esac
LOG_DIR="$(mktemp -d "${TMPDIR:-/tmp}/rerun.XXXXXX")"
trap 'echo; echo "run logs: $LOG_DIR"' EXIT
echo "command: $CMD"
echo "runs: $RUNS"
echo
failures=0
strip=""
first_failure=""
for i in $(seq 1 "$RUNS"); do
log="$LOG_DIR/run-$i.log"
if bash -c "$CMD" >"$log" 2>&1; then
strip="${strip}."
else
strip="${strip}X"
failures=$((failures + 1))
[ -z "$first_failure" ] && first_failure="$log"
fi
# Redraw in place only for a human at a terminal. Piped — which is how an agent
# runs this — `\r` is not a cursor move, so every iteration lands as another
# copy of the strip and a 40-run measurement becomes 40 lines of near-identical
# noise in the reader's context.
if [ -t 1 ]; then
printf "\r [%-${RUNS}s] %d/%d failures: %d" "$strip" "$i" "$RUNS" "$failures"
fi
if [ "$STOP_AFTER" -gt 0 ] && [ "$failures" -ge "$STOP_AFTER" ]; then
echo
echo " stopping early: reached $STOP_AFTER failure(s)"
RUNS="$i"
break
fi
done
echo
echo " runs: [$strip] . = pass X = fail"
echo
# Wilson score interval at 95%. Preferred to the normal approximation because the
# rates that matter here are near zero, where the normal interval goes negative
# and stops meaning anything.
python3 - "$failures" "$RUNS" <<'PY'
import math, sys
failures, runs = int(sys.argv[1]), int(sys.argv[2])
p = failures / runs if runs else 0.0
z = 1.96
denom = 1 + z * z / runs
centre = (p + z * z / (2 * runs)) / denom
margin = (z * math.sqrt(p * (1 - p) / runs + z * z / (4 * runs * runs))) / denom
low, high = max(0.0, centre - margin), min(1.0, centre + margin)
print(f"observed failure rate: {failures}/{runs} = {p * 100:.1f}%")
print(f"95% interval: {low * 100:.1f}% – {high * 100:.1f}%")
if failures == 0:
print(
f"\nNot reproduced in {runs} runs. The true rate could still be as high as "
f"{high * 100:.1f}% — report this as 'not reproduced', not as 'not flaky'."
)
elif failures == runs:
print(
"\nFailed every run. This is a broken test, not a flaky one — diagnose it from "
"the failure itself rather than measuring a rate that is already 100%."
)
else:
print(
"\nReproduced. Next: run the test alone many times, then inside its file, then "
"inside the whole suite. Only-fails-in-the-larger-set means ordering or shared "
"state; fails-alone means timing or environment."
)
PY
if [ -n "$first_failure" ]; then
echo
echo "first failing run: $first_failure"
echo "── tail ──"
tail -n 25 "$first_failure"
fi
[ "$failures" -eq 0 ] && exit 0 || exit 1
More skills from Contexory
- Doc Drift DetectorChecks a document's checkable claims against the code it describes — file paths that no longer exist, commands that are no longer defined, flags and symbols that have been renamed, version numbers…
- Repo Onboarding MapProduces the first-thirty-minutes map of a whole repository — how it is laid out, how to run and test it, which files carry the most change, and where the decisions were written down. Reads what the…
- Test Gap FinderFinds the untested code that actually matters, by ranking coverage gaps against how often each file changes. Reads an existing coverage report when there is one and falls back to structural pairing…
- Regression Risk MapperMaps what a change can reach — a change being considered as readily as one already made, since the question is usually asked before the edit exists. Finds the symbols involved, traces every call site…
- Pr Self ReviewReviews your own diff before anyone else has to. Inventories every changed file, finds the ones that gained behaviour without gaining a test, flags newly exported surface, and separates what the diff…
- Error TriageLocates the cause of a runtime failure inside this repository. Maps stack frames to real source files, separates first-party code from vendored frames, and surfaces the recent changes to the line…