Community라이팅 & 에디팅github.com

windchillscalanthes-ship-it/flaky-test-detective

A Claude Agent Skill that finds the root cause of flaky (non-deterministic) tests and rewrites them to be deterministic. pytest · Jest · RSpec · Go · JUnit.

flaky-test-detective란 무엇인가요?

flaky-test-detective is a Claude Code agent skill that a Claude Agent Skill that finds the root cause of flaky (non-deterministic) tests and rewrites them to be deterministic. pytest · Jest · RSpec · Go · JUnit.

지원 대상Claude Code~Codex CLI~Cursor
npx skills add windchillscalanthes-ship-it/flaky-test-detective

Installed? Explore more 라이팅 & 에디팅 skills: steipete/notion, affaan-m/seo, affaan-m/brand-voice · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Flaky Test Detective

Catch the test that passes and fails on the same code — and rewrite it so it's deterministic, before it trains your team to ignore red builds.

A flaky test is worse than no test. It fails once, someone reruns CI, it passes, and everyone learns that red doesn't mean broken. Soon a real regression sails through behind a "just rerun it." The cause is almost never the code under test — it's a hidden dependency on something the test doesn't control: the clock, the random seed, the order tests run in, a shared row in the database, a real network call, the iteration order of a map. This skill finds that hidden input, explains why it makes the test non-deterministic, and rewrites the test to control it.

When to use this

  • A test fails intermittently — red on one run, green on the next, no code change.
  • A test is green locally but red in CI (or vice versa), or only fails under parallelism or a particular order.
  • Someone says "just rerun it," "it's flaky," or "passes on retry."
  • You are writing or reviewing a test and want to catch non-determinism before it's committed.
  • You want to harden a suite — find the latent flakiness before it fires.

If a test fails every time, it's not flaky — it's a real failure or a genuine bug; fix the code, don't stabilize the test. And never "fix" flakiness by adding a retry or a sleep that hides it — that buries the signal. The goal is a test that's deterministic, not one that's usually green.

Workflow

  1. Establish context. Identify the language / framework / runner (pytest, Jest/Vitest, RSpec/Minitest, Go testing, JUnit, PHPUnit, …) and where it flakes — locally, in CI, only in parallel, only in a certain order, only at a certain time of day. Where and when it flakes is the biggest clue to the cause.

  2. Reproduce the non-determinism. A flaky test you can't fail on demand isn't fixed, just quiet. Force it: run it many times, run it in random order, run it in isolation vs. with the full suite, pin it to one worker, or shift the clock/timezone. reference/detection.md has the exact commands per framework.

  3. Classify the root cause against the catalog below, and assign a severity:

    • 🔴 Flaky — a concrete non-deterministic input reaches an assertion; it will fail eventually. Must be made deterministic.
    • 🟡 Latent — non-deterministic in principle but currently masked (a seed that happens to be stable, an order that happens to hold, a fast-enough CI). Name the condition that would break it.
    • 🟢 Deterministic — the test controls its inputs. No change.
  4. Fix the root cause — don't paper over it. For each 🔴/🟡, give:

    • why it's non-deterministic — the exact uncontrolled input and how it reaches the assertion,
    • the deterministic rewrite as runnable code (framework-idiomatic), and
    • the result (e.g. "order-dependent → isolated" or "flaky 1-in-20 → green 1000×").
  5. Report using the output template at the end. Lead with the 🔴s.

For language- and cause-specific depth, load the reference file relevant to the test in front of you:

  • reference/root-causes.md — the eight root-cause families in depth: how each makes a test flaky, how to detect it, and the canonical fix.
  • reference/frameworks.md — per-framework mechanics: controlling the clock, seeding RNG, randomizing/pinning test order, isolating state, and mocking the network in pytest, Jest/Vitest, RSpec/Minitest, Go, JUnit, and PHPUnit.
  • reference/detection.md — how to reproduce flakiness on demand: rerun loops, order randomization, isolation/bisection, parallelism, and reading CI signals.
  • reference/patterns.md — the deterministic-test principles: hermeticity, control the clock/RNG/network/order, per-test isolation, and the detect→fix→quarantine lifecycle.

Read the reference for the framework in front of you rather than guessing — the mechanics differ (freezing time, seeding, and order control are done completely differently in pytest vs. Jest vs. Go).

Root-cause catalog (the high-frequency offenders)

"Uncontrolled input" = anything the test's result depends on that the test does not fix. Flakiness is always an uncontrolled input reaching an assertion.

#Root causeWhy it's non-deterministicDeterministic fix
1Test-order dependence / shared mutable stateA test relies on state left by another (a global, a class attr, a module singleton, an un-rolled-back row). Passes in one order, fails in another.Isolate: fresh fixtures per test, reset globals in teardown, wrap each test in a transaction rollback. Randomize order in CI to catch it.
2Time & timezonenow()/Date.now()/today() in the test or code — fails at midnight, month/year boundaries, under DST, or in CI's timezone.Freeze the clock (freezegun/time-machine, Jest fake timers, timecop, Go injable clock); pin TZ; never assert against the real wall clock.
3Unseeded randomnessrandom(), uuid4(), faker, shuffled data — a rare value trips an assertion once in N runs.Seed the RNG deterministically per test; inject IDs/fakes; assert on properties, not a specific random value.
4Async race conditions / sleep-based waitssleep(100) then assert — works until CI is slower; unawaited promises; callbacks asserted before they run; event ordering.Wait on the condition, not a duration (waitFor/await the actual signal); await every promise; use fake timers to control scheduling.
5Real network / external servicesA live HTTP/API/DNS call — fails on latency, rate limits, downtime, or changed data.Mock at the boundary (responses/respx, nock/msw, VCR/WireMock); never hit a real network in a unit test.
6Unordered-collection assertionsAsserting a specific order from a set/dict/map, a DB query without ORDER BY, os.listdir, or concurrent results.Sort before comparing, assert set-equality/contains, or add an explicit ORDER BY. Don't depend on incidental ordering.
7Leaked global state / resource bleedEnv vars, singletons, module caches, the CWD, open connections/files/ports mutated and not restored — pollutes later tests.Save/restore around each test (fixtures/beforeEach/t.Cleanup); close every resource; monkeypatch with auto-undo.
8Too-tight timeouts / perf assumptionsA hardcoded timeout or assert elapsed < X that holds on a fast laptop but not a loaded CI runner.Remove wall-clock perf assertions from correctness tests; if a timeout is needed, make it generous and condition-based, not a tight constant.
9Floating-point exact equalityassert result == 0.3 after arithmetic — rounding makes it occasionally unequal.Assert with a tolerance (pytest.approx, toBeCloseTo, epsilon compare).
10Locale / environment assumptionsNumber/date/string formatting, case-folding, path separators, or filesystem case-sensitivity that differ across machines/CI.Pin locale/encoding in the test; use locale-independent formatting; don't assert OS-specific paths.
11Shared fixtures / DB not isolatedTests share a database, cache, or temp dir and step on each other under parallelism.Per-test transactions/rollback, unique schemas/namespaces per worker, tmp_path per test; no shared writable fixture.
12Hidden concurrency in the code under testBackground threads, timers, or async tasks that finish (or don't) unpredictably relative to the assertion.Make the boundary awaitable/injectable; drain/flush the queue deterministically; inject a controllable executor/clock.

The 🟡 tier is where flakiness hides before it fires: a test that's order-dependent but happens to run last, a seed that's currently stable, a sleep that's currently long enough. Name the condition that flips it to 🔴 — that's the fix waiting to happen.

Guiding principles

  • Flakiness is an uncontrolled input, not bad luck. Every flaky test depends on something it doesn't fix: the clock, the seed, the order, the network, a shared row. Find that input and control it — freeze it, seed it, isolate it, mock it.
  • Reproduce before you fix. If you can't make it fail on demand (rerun loop, random order, isolation), you haven't found the cause — you've hidden it.
  • A test must own its state. It should pass alone, in any order, in parallel, a thousand times. Setup creates what it needs; teardown restores what it touched.
  • Control the four usual suspects. Clock, randomness, network, and order are the source of most flakiness — make each one deterministic by default.
  • Wait on conditions, never on time. sleep(n) is a race you'll lose on a slow runner. Await the actual signal or poll the condition with a generous cap.
  • Never fix flakiness with a retry. Retries and blanket sleeps convert a visible defect into a silent one and destroy the suite's credibility.
  • Quarantine, don't delete. If a flaky test can't be fixed immediately, mark it (skip/quarantine) with a tracking issue so it stops blocking — but keep it, and keep the signal that it's broken.
  • Right-size. A genuinely order-independent, hermetic test needs none of this — don't bolt determinism theater onto a test that already controls its inputs.

Output template

## Flaky Test Review

**Stack:** <language / framework / runner>  ·  **Flakes:** <CI only / in parallel / random order / at time X>  ·  **Tests reviewed:** <n>

### 🔴 <file::test name> — FLAKY
**Root cause:** <order dependence / time / randomness / async race / network / ordering / global state / timeout / …>
**Why non-deterministic:** <the exact uncontrolled input and how it reaches the assertion>
**Reproduce:** <the command that makes it fail on demand — random order, rerun loop, isolation, clock shift>
**Deterministic rewrite:**
<runnable, framework-idiomatic code>
**Result:** <e.g. "order-dependent → isolated; green in 1000 random-order runs">

### 🟡 <file::test> — LATENT
**Root cause / Why it's currently masked / The condition that would flip it to flaky / Recommendation**

### 🟢 <file::test> — DETERMINISTIC
<one line: it controls its inputs — no change>

---
**Summary:** <n flaky, n latent.> <the worst uncontrolled input in one line.> <bottom line: stable to merge, or fix test X first.>

Always give the deterministic rewrite and the command to reproduce — a review that says "this is flaky" without the controlled-input fix and a way to prove it just hands the hard part back. Naming the uncontrolled input is the expertise the skill supplies.

관련 스킬

steipete/notion

Notion CLI/API for pages, Markdown content, data sources, files, comments, search, Workers, and raw API calls.

community

affaan-m/seo

Audit, plan, and implement SEO improvements across technical SEO, on-page optimization, structured data, Core Web Vitals, and content strategy. Use when the user wants better search visibility, SEO remediation, schema markup, sitemap/robots work, or keyword mapping.

community

affaan-m/brand-voice

Build a source-derived writing style profile from real posts, essays, launch notes, docs, or site copy, then reuse that profile across content, outreach, and social workflows. Use when the user wants voice consistency without generic AI writing tropes.

community

affaan-m/crosspost

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

community

affaan-m/x-api

X/Twitter API integration for posting tweets, threads, reading timelines, search, and analytics. Covers OAuth auth patterns, rate limits, and platform-native content posting. Use when the user wants to interact with X programmatically.

community

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

community