Skip to content
OpenAgentsbeta
text
1# Flaky Test Triage
2
3## 1. Establish the rate before touching anything
4
5"Sometimes fails" is not a measurement. Run it `runs` times (default 50) and record the
6failure rate.
7
8```bash
9# Adapt to your runner. The point is repetition and a count.
10for i in $(seq 1 50); do <run one test> >/dev/null 2>&1 || echo "fail $i"; done | wc -l
11```
12
13Run it three ways, because the differences are the diagnosis:
14
15| Condition | What a failure here implies |
16|---|---|
17| Alone, repeated | Self-contained nondeterminism: time, randomness, ordering |
18| With the full suite | Shared state or pollution from another test |
19| Under parallelism | Resource contention: ports, files, database rows |
20
21A test that only fails in one of the three has already told you which family of cause
22you are in.
23
24## 2. Capture a failure with enough detail to read
25
26Do not debug from the assertion message alone. On failure, capture:
27
28- The full diff between expected and actual, not a truncated boolean.
29- Timestamps at each step, to spot a timing edge.
30- The random seed and the test execution order.
31- Relevant environment: time zone, locale, machine, concurrency level.
32
33If your runner cannot report the seed and the order, fix that first. Without it, an
34ordering bug is unreproducible by construction.
35
36## 3. Classify the cause
37
38Work through `causes.md`. Match on the tell, not on intuition. The families, roughly in
39order of frequency:
40
411. Order dependence and shared state
422. Time and clock assumptions
433. Concurrency and race conditions
444. Unseeded randomness
455. Resource contention
466. External dependencies
477. Genuine product bugs that only surface under a specific interleaving
48
49The last one matters most: **a flaky test is sometimes a correct test finding a real
50race.** Rule that out before assuming the test is at fault. Silencing it would be
51deleting a bug report.
52
53## 4. Prove the cause
54
55A hypothesis is not confirmed until you can turn the failure on and off:
56
57- Make it fail every time. Force the ordering, pin the clock, hold the lock, use the
58 seed that fails. If you cannot make it fail deterministically, you have not found it.
59- Then apply the fix and make it pass every time.
60
61Skipping this step is how a test gets "fixed" three times and stays flaky.
62
63## 5. Fix the cause, not the symptom
64
65Ranked, best first:
66
671. **Remove the nondeterminism.** Inject the clock, seed the randomness, fix the shared
68 state, await the actual condition.
692. **Make the test wait for a condition, never a duration.** `sleep` is a race with
70 extra steps.
713. **Isolate the resource.** A unique port, a temp directory, a per-test schema.
724. **Narrow the assertion** if it was over-specifying, for example asserting an
73 unordered collection in order.
74
75Never acceptable as a fix: a retry annotation, a longer sleep, or a loosened assertion
76that would no longer catch the original bug.
77
78## 6. Quarantine honestly, if you must
79
80Sometimes the fix cannot land today. Then:
81
82- Move the test out of the blocking suite, but keep running it and keep reporting it.
83- File an issue with the failure rate you measured and everything you established.
84- Put an expiry on it. A quarantine with no date is a deletion with extra steps.
85- Never quarantine a test whose failure you have not yet explained. That is where a
86 real bug goes to be forgotten.
87
88## 7. Verify
89
90Re-run the same three conditions from step 1, at the same repetition count. Report the
91before and after rates as numbers. "Seems better" is not a result.
92

Keyboard shortcuts

Focus search
/
Go to Explore
ge
Go to Home
gh
Go to Tags
gt
Go to Collections
gc
Show this help
?
Close suggestions or this dialog
Esc