How to Reproduce Bugs Reliably Before You Hit Submit
A bug that only works on your laptop is not a report yet. It is a hunch. Triagers discard hunches. They keep findings they can replay in under ten minutes with the instructions you wrote.
I've rejected my own drafts overnight more times than I care to admit. Cold reproduction is the filter that separates "interesting proxy noise" from money.
Write the recipe while the session is still hot
Do not trust memory. Before you celebrate, open a clean browser profile or a second proxy history and redo the chain from zero. Capture:
- Program URL, asset, and environment (
prod,staging, invited sandbox) - Two owned accounts with roles if the bug crosses tenants
- Exact path, method, and query string
- Cookies or tokens that matter — and which ones you rotated
- The single response line that proves impact (status, body snippet, redirect)
If step five only works after you left a tab open for twenty minutes, that is part of the bug. Write it down. Latency and race windows vanish the moment you "simplify" the steps for elegance.
Name your canaries in the notes: victim+repro9182@yourdomain, invoice ID INV-REPRO-9182, project slug repro-9182. When triage searches logs, those strings should light up like a runway.
Strip luck out of the sequence
Ask yourself: what did I do that a stranger would not know to do?
Clear storage between attempts. Disable extensions that rewrite headers. Try a different IP only if the program allows it and the bug might be edge-cached. Prefer a short curl or Repeater sequence over "click around until it breaks." UI clicks are fine for discovery; they are terrible as the only proof.
For stateful bugs, reset the fixture. Create a fresh invite, a new cart, a new project. Reusing yesterday's half-broken object makes your steps look flaky when the real issue is leftover data. If the bug needs a specific feature flag, say how you obtained it — invite code, plan tier, or admin toggle on a test org you own.
GraphQL and mobile clients hide the real call behind SDKs. Copy the wire request, not the button label. If the app sends x-client-version, include it. Version skew is a classic "works for researcher, fails for triage" trap.
Label the expected outcome
Every step needs an observable. "Send request A" is incomplete. "Send request A; response is 403 with body tenant mismatch" is usable. "Then open /admin as account B and see A's invoice ID" closes the loop.
If the bug is intermittent, say so up front and give the success rate you measured (for example, 4 of 10 races within two seconds). Triagers hate surprise non-repro more than they hate honest races. Include sleep intervals if timing matters. "Immediately" is not a duration.
When impact depends on email, webhook, or async jobs, note the wait. "Within 60 seconds a Slack webhook receives the canary" saves everyone from refreshing forever.
Sanity-check before submit
Run the steps once from your notes alone — not from the Burp tab you already know. Hand the draft to a friend if you can. If either of you stalls, the report will stall.
Check scope one last time. A perfect reproduction of an out-of-scope host is still a wasted ticket. Check for duplicates with your canary keywords and the endpoint path.
Reliable reproduction is not bureaucracy. It is how you stop arguing with triage and start getting paid for bugs that actually exist.