Crash Fuzzer
Open the real app — the actual build, no mocks, no stubbed backends — drive it
with fuzzed but realistic interactions until it crashes, find the true root
cause, and ship a small, verified fix PR. This is meant to run once per app per
day as an unattended routine and also on demand via /crash-fuzzer.
Read reference.md for platform launch/fuzz/capture recipes and the truth-table
format.
Golden rules
- Real app, no mocks. Launch the shipping build against real (or staging) services. A crash that only reproduces against mocks is not a crash worth a PR; a crash the fuzzer can't reproduce on the real app doesn't get filed.
- One crash → one PR. Keep each PR to a single root cause, small and revertible. Don't bundle unrelated fixes.
- Root cause, not symptom. A null-guard that hides the crash is not a fix. Trace to why the bad state happened and fix that. Say so explicitly in the PR.
- Every PR runs
/verifyand carries a repro + truth table. No exceptions. - All updates go to the top-level
Fuzzerthread in the designated channel (configure the channel per repo — see Configuration).
The routine
1. Pick the target and launch the real app
- Determine the platform from the repo (iOS/Android/Desktop/web/CLI/Agent SDK)
and launch the release-configured build on a simulator, emulator, or
attached device. See
reference.mdfor exact commands per platform. - Point it at real or staging backends. Record the build SHA, OS/version, device, and locale — these go in every repro.
2. Fuzz realistic interactions
- Drive the UI (or CLI/API surface) with plausible sequences, not random bytes: tap/scroll/type/rotate/background-foreground/deep-link/paste, permission prompts, network flaps, low-memory, rapid navigation, empty and huge inputs, non-Latin text, offline→online transitions.
- Prefer the platform's own fuzz/monkey harness where one exists (e.g. Android
monkey, XCUITest random walk, Playwright with a seeded random driver, a fuzzed CLI arg/stdin generator). Seed the RNG and log every action so any crash is replayable. - Watch for crashes, ANRs/hangs, fatal logs, unhandled promise rejections, and non-zero exits. Capture the stack, logs, screenshot/recording, and the exact action sequence.
3. Reduce to a minimal repro
- Replay the logged action sequence, then delta-debug: remove steps until you have the shortest sequence that still crashes. That reduced sequence is the repro you ship.
- Confirm it reproduces from a clean launch at least twice.
4. Root-cause and write a failing test
- Trace the stack to the real cause (bad state transition, unchecked optional, race, off-by-one, contract violation). Model the state that led there.
- Add an automated test that fails on
mainand passes with the fix — a unit test, or a scripted UI test replaying the reduced repro.
5. Fix, verify, and open the PR
- Make the smallest fix that addresses the root cause.
- Run the project's
/verify(build + lint + tests + whatever/verifycovers for this repo) and paste the result into the PR. - Open the PR with the body template below.
PR body template
## Crash: <one-line summary>
**Environment:** <platform> · build <sha> · <OS/version> · <device/sim> · <locale>
### Repro (minimal, seeded)
seed: <rng-seed>
1. <step>
2. <step>
3. <step> ← crashes here
<stack trace excerpt / fatal log>
### Root cause
<why the bad state occurred — not just where it threw>
### Fix
<the smallest change that fixes the root cause>
### Truth table (verified e2e)
| Scenario | Input / state | Before | After |
| --- | --- | --- | --- |
| repro sequence | seed <n> | 💥 crash | ✅ handled |
| adjacent case A | ... | ✅ | ✅ (unchanged) |
| adjacent case B | ... | ✅ | ✅ (unchanged) |
### /verify
<pasted output — build ✓ lint ✓ tests ✓>A failing-then-passing test is attached in the diff. The truth table must show the repro fixed and neighbouring cases unchanged (no regressions).
Slack updates (Fuzzer thread)
- Maintain one top-level thread titled
Fuzzerin the designated channel. Post the day's run as a reply: target app, build, how long fuzzed, crashes found, PRs opened (with links), and "no crashes found" when clean. - Never start a new top-level message per run — keep the history in the one thread so trends are visible.
Configuration (per repo)
Read these from the repo's routine config / env; fall back to asking:
SLACK_CHANNEL— where theFuzzerthread lives.PLATFORM/ launch target — else infer from the repo.VERIFY_CMD— defaults to the/verifycommand; else the repo's build+test.FUZZ_BUDGET— wall-clock or action count per run.
Guardrails
- Never commit secrets, device tokens, or captured PII from real-backend runs; scrub logs/screenshots before attaching.
- If you can't reproduce a crash deterministically, don't open a PR — post the flaky signal to the Fuzzer thread for a human instead.
- Don't "fix" by swallowing the error. Root cause only.
Provenance
Pure process/pattern skill — no API, no bundled code. Distilled from the public
Claude Code team routine described by Boris Cherny
(https://x.com/bcherny/status/2088014489438621990): daily e2e crash-fuzzing that
runs the real apps, opens root-cause fix PRs with a repro + truth table, runs
/verify, and reports in a top-level Fuzzer Slack thread.