# Find the root cause of a bug before writing any fix

> A debugging prompt that makes the AI rank hypotheses, name a confirming experiment and only then propose a minimal fix with a regression test.

- **Author:** [Abhay Pratap Mishra (@abhay)](https://promptabide.com/abhay)
- **Tested on:** Claude · Opus 5.5
- **You fill in:** `symptom`, `expected`, `code`, `tried`
- **Published:** 2026-09-22
- **Updated:** 2026-09-24
- **Tags:** `coding`, `debugging`, `testing`
- **Keywords:** root cause analysis prompt for bugs, ai debugging prompt before fixing, find why code is failing with ai, debug timezone bug in javascript, chatgpt claude debugging workflow
- **Views:** 665
- **Likes:** 30

**Best for:** Developers stuck on a bug where the obvious patch didn't work, or who want to understand the failure before shipping a fix.

## Prompt

```
I have a bug. Do not propose a fix yet.

Symptom: {{symptom}}
Expected: {{expected}}
Code:
{{code}}
Already tried: {{tried}}

Work in this order:
1. Restate the symptom in one sentence, precisely: which input produces which wrong output.
2. List up to 4 hypotheses for the root cause, ranked by likelihood. For each, point to the exact line and explain the mechanism that would produce this exact symptom. "Could be a race condition" is not a mechanism.
3. Check each hypothesis against what I've already tried. Drop any that my attempts rule out, and say why.
4. For the top hypothesis, give the smallest experiment that would confirm or kill it (a log line, a one-line REPL check, a single test) and the result I should see in each case.
5. Only then: the minimal fix for the top hypothesis as a diff, plus one regression test that fails before the fix and passes after.
6. Tell me where else in a codebase this same root cause usually hides, so I can grep for it.

If the code shown isn't enough to decide, say what you would need to see instead of guessing.
```

## Variables

- `{{symptom}}` — What goes wrong, as precisely as you can say it (e.g. Orders placed between midnight and 5:30am IST show up under the previous day in the daily report)
- `{{expected}}` — What should happen instead (e.g. Each order is counted on the calendar day it was placed in India)
- `{{code}}` — The smallest chunk of code that contains the bug, pasted in full (e.g. The groupOrdersByDay function (15 lines))
- `{{tried}}` — What you have already tried or ruled out (e.g. Checked the DB timestamps are right; setting TZ=Asia/Kolkata on the server changed nothing)

## Example input

````
symptom: Orders placed between midnight and 5:30am IST show up under the previous day in the daily sales report.
expected: Each order is counted on the calendar day it was placed in India.
code:
```ts
// report/groupOrders.ts
type Order = { id: string; createdAt: string; amount: number } // createdAt e.g. "2026-09-23T20:15:00Z"

export function groupOrdersByDay(orders: Order[]) {
  const buckets: Record<string, Order[]> = {}
  for (const order of orders) {
    const day = new Date(order.createdAt).toISOString().slice(0, 10)
    ;(buckets[day] ??= []).push(order)
  }
  return Object.entries(buckets)
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([day, items]) => ({
      day,
      count: items.length,
      total: items.reduce((s, o) => s + o.amount, 0),
    }))
}
```
tried: Checked the DB timestamps are correct (stored in UTC with Z). Setting TZ=Asia/Kolkata on the server changed nothing.
````

## Output

*Produced by Claude · Opus 5.5.*

````
**1. Symptom, restated**
An order created at, say, 2026-09-24 01:10 IST (stored as `2026-09-23T19:40:00Z`) is bucketed under `2026-09-23` instead of `2026-09-24`.

**2. Hypotheses, ranked**
1. **`toISOString()` always renders UTC** (line 7). IST is UTC+5:30, so every order from 00:00 to 05:29 IST has a UTC date one day earlier. The window you describe is exactly the size of the offset. That's the tell.
2. **`createdAt` sometimes lacks the `Z`.** `new Date("2026-09-23T19:40:00")` is parsed as *local* time, which would shift days on some rows. It's less likely, because you confirmed the stored values carry `Z`.
3. **The report UI re-parses `day`.** `new Date("2026-09-24")` is UTC midnight. Rendered in IST that's still the 24th, so this can't produce your symptom. It would only matter for users west of UTC.

**3. Against what you tried**
Setting `TZ=Asia/Kolkata` changing nothing *supports* hypothesis 1: `toISOString()` ignores the process timezone entirely. Hypothesis 2 is ruled out by your DB check.

**4. Confirming experiment**
```bash
node -e 'console.log(new Date("2026-09-23T19:40:00Z").toISOString().slice(0,10))'
```
If hypothesis 1 is right, this prints `2026-09-23`, although the order is on the 24th in India. If it prints `2026-09-24`, I'm wrong and I'd need to see how the report calls this function.

**5. Minimal fix**
```diff
+const istDay = new Intl.DateTimeFormat("en-CA", {
+  timeZone: "Asia/Kolkata", year: "numeric", month: "2-digit", day: "2-digit",
+})
 ...
-    const day = new Date(order.createdAt).toISOString().slice(0, 10)
+    const day = istDay.format(new Date(order.createdAt)) // en-CA gives YYYY-MM-DD
```
Regression test:
```ts
it("buckets a 01:10 IST order on the IST calendar day", () => {
  const [row] = groupOrdersByDay([{ id: "1", createdAt: "2026-09-23T19:40:00Z", amount: 100 }])
  expect(row.day).toBe("2026-09-24")
})
```
It fails on the current code (`2026-09-23`) and passes after the fix. If you'll ever report for other regions, pass the timezone in as a parameter instead of hard-coding it.

**6. Where the same bug usually hides**
- `grep -rn "toISOString().slice(0, 10)"` and `.split("T")[0]`
- SQL `DATE(created_at)` or `created_at::date` running in a UTC session
- "Today's orders" filters built with `new Date().setHours(0,0,0,0)` on a UTC server
- Cron jobs scheduled at "midnight" in server time
````

## Why it works

The line **"Do not propose a fix yet"** stops the model's reflex to patch the first plausible cause. Asking for **a mechanism that produces this exact symptom** forces it to match the evidence (here, a 5.5-hour window equal to the IST offset) rather than list generic suspects. Step 3, **checking hypotheses against what you already tried**, turns your failed attempts into evidence: the `TZ` change failing is what confirms the diagnosis. The **fails-before, passes-after test** makes the fix verifiable, and step 6 turns a single fix into a codebase-wide sweep.

## When not to use it

Skip it for typos and obvious one-line errors, where the ceremony slows you down. It also struggles when the bug depends on state you can't paste: production data, concurrency across services, or a flaky environment. There, gather logs and a reproduction first. For bugs inside a large repo, a coding agent that can run the experiment itself will beat pasting code into chat.

---

Canonical HTML: https://promptabide.com/bides/root-cause-a-bug-before-fixing-it
Agent guide: https://promptabide.com/llms.txt · https://promptabide.com/agent-instructions.md
Sitemap: https://promptabide.com/sitemap.xml
