What does bughunt do?
Unit tests and most UI tests check the code paths their authors thought of, often through test hooks that skip real clicks and keys. People don't use an app that way. They click where the hook doesn't, press Undo after everything, work in a narrow window, lose the network halfway through, and open the strange file a colleague sent. Most of the bugs they meet were already there, and nothing was looking.
This skill looks. It drives the real app with real input, one area at a time, and records every confirmed bug twice: as a failing test that stays in the repo, so the bug can't come back unnoticed, and as an entry in the repo's bug list, so someone fixes it. A hunt doesn't fix product code. It finds, proves and writes down.
Modes
- Hunt (the default): pick one area, hunt it, record the findings, update the rotation. One area per run keeps a hunt deep enough to find real bugs.
- Set up: the repo has no way to drive the app end to end, or it lacks the boundaries in
references/boundaries.md. Build the smallest walkthrough lane that can run one feature on every surface (references/walkthroughs.md), then hunt. - Sweep: run every existing walkthrough and report what is new, what changed and what was fixed since the last run. A sweep finds regressions, not new kinds of bugs; pair it with hunts.
- Rules: add one of the rules in
references/invariants.mdto the lane, so it runs on every step of every walkthrough. A single rule (for example "Undo restores the exact previous state") can surface a dozen bugs across features nobody would think to script.
1. Learn the ground rules first
Read the repo's instruction files (CLAUDE.md, AGENTS.md, CONTRIBUTING, any rules or handoff notes), the README of whatever runs the app, and the existing end-to-end tests. Find out:
- How to start each surface the app has: dev server, desktop build, mobile simulator. A surface is one host (web, desktop, mobile) in one look (light, dark, high contrast) at one size (wide, narrow, phone).
- Where bugs are tracked (a bugs file, a to-do list, an issue tracker) and the format entries use there. Write in that format.
- What a test must never touch: real accounts, real user files, production data, real email, payments, the user's clipboard, printer and browser, the installed copy of the app. Read
references/boundaries.mdand put every boundary that applies in place before driving anything. This is the part of a hunt that can do real damage, so it comes first. - Whether the repo forbids anything else (pushing, network access, paid services, starting agents). Those rules win over this skill.
Ask only when the answer changes what you may touch. Otherwise choose, and write the assumption in the hunt report.
2. Pick the area and list what it does
Use the area the person named. Otherwise read bughunt/areas.md (create it from templates/areas.md on the first run) and pick, in this order: an area never hunted, then one changed since its last hunt (git log on its paths), then the stalest. An area is a set of features a person uses together: the document editor, the file browser, sign-in and accounts, settings, sharing, payments.
List the area's features from whatever the repo has: a feature registry, the menus and command list, routes, the help pages, the code's entry points. Each feature gets at least one pass on every surface.
3. Hunt
For each feature, first do what a person does with it, start to finish, on realistic synthetic files. Then work through references/checklist.md. It is a list of hostile-user moves that break apps over and over: Undo after every step, keyboard only, narrow window, dark mode, offline midway, the biggest and the most broken file, a round trip through another format, the same thing in two tabs, a failure on purpose.
Drive the app with real input events (clicks, key presses, drags, typing) at a person's pace. A script acts faster than a person, so give a click a short beat before the next action, and leave half a second between two changes you expect to undo separately. Otherwise you'll file timing artefacts as bugs.
Watch every step for:
- the wrong result, or a right result that doesn't stay (gone after reload, save, Undo or switching tabs)
- lost work: edits, settings, selection, scroll position
- Undo that takes back too much, too little, or nothing
- a command that does nothing, works only from one of its entry points, or isn't disabled when it can't run
- raw error text: stack traces, error codes, internal paths
- console errors, crashes, blank windows, hangs
- text that is too faint, clipped, overlapping, or broken mid-word (the screenshot checks in
references/invariants.mdcatch most of this on their own) - the same step behaving differently on another surface
Write each candidate down when you see it, with the surface, the step and what you expected. Then carry on; confirm at the end of the feature.
4. Confirm before you file
A hunt is only as good as its findings are true. For each candidate:
- Reproduce it twice from a clean state, at a person's pace, by hand or with a minimal script. If it doesn't reproduce, drop it or file it as "Unverified:" with what you saw and where.
- Rule out the test. Timing (see above), a selector that matched the wrong element, the test's own stubs, a window that wasn't focused, the pointer resting on something with a hover state. A finding that comes and goes with where the pointer rests is real but needs a fixed pointer position in its test.
- Find the cause when it's cheap. Read the code at the likely spot and name the file and function. Don't spend long here; the fixer will.
- Check the tracker. If an entry already covers it, add the new surface or detail to that entry instead of a new one.
- Group by cause. Several symptoms often share one cause (one missing key binding can break Undo in five editors on one host). Say so, so the fix happens once.
5. Record each confirmed bug
- A failing test first. Add a step to the feature's walkthrough (or a new walkthrough) whose expectation states what should happen. Run it and check that it fails, and fails for the bug's reason, not for something else. It passes once the bug is fixed. Never weaken an existing expectation or screenshot check to make a run pass. If a check is wrong, say why in the report and leave it for a person.
- Then the bug entry, at the end of the repo's bug list, in its format, using
templates/bug-entry.md: the symptom in a user's words, what happens against what should, the measured numbers, where in the code, which test guards it, and on which surfaces it shows. - No product code changes in a hunt, unless the person asked for fixes. Fixing as you go hides the finding and mixes two kinds of review.
- Commit the new tests and the bug entries if the repo's rules allow it, staging files by name. Don't push unless the person said to.
6. Report and rotate
Write the hunt report from templates/hunt-report.md: the area, the surfaces, every finding with its test and entry, the candidates you dropped and why, what you couldn't test, and suggested fix groups by cause. Update the area's row in bughunt/areas.md (date, commit, findings).
End by telling the person, in plain language: how many bugs were confirmed, the worst three, the fix groups, and the next area the rotation would pick.
Keep finding them
A finished hunt leaves tests that catch the same bugs coming back. New bugs need new looking:
- Hunt again in rotation. Re-hunt an area after big changes to it.
- Turn a bug that keeps showing up in new places into a rule (
references/invariants.md) so every step checks it. - Every bug a person reports becomes a walkthrough step before it is fixed.
- Run the whole lane before every release and, if the repo allows a scheduler, every night. Report only new and changed failures, so a known bug doesn't bury a new one.
When the work is big
If you can split work across models or sessions, hunt with your strongest model and confirm with a different one in a fresh session. The second pass re-runs each failing test from a clean state and reads each entry cold. That catches the confident false positive that one pass alone tends to keep.