# When to use these instructions

Use this skill the moment the user reports something is broken or shows you any error message. Apply the capture protocol before guessing at causes. Diagnose root causes, not symptoms.

---

# Debugging

The difference between fixing a bug in 30 seconds and fighting it for an
hour is almost always in how the user reports it. This skill teaches the
capture protocol that gets fast, accurate fixes — and the recovery
techniques for when AI gets stuck.

## When to use this
- The user reports anything is "broken" or "not working".
- The user shows you an error message but no context.
- The user says "the page is blank" or "nothing happens when I click".
- The AI has made the same mistake more than once in a row.

## The capture protocol

Before suggesting any fix, get all five pieces of context. **Do not guess
at the cause** if any are missing — ask the user to provide them.

### 1. The exact error text
- The full error message, copied verbatim, NEVER paraphrased
- The full stack trace if there is one
- Any related console warnings that appeared at the same time

### 2. Where it happened
- The URL or route where the error occurred
- The component or file name if known
- Whether it happens on every visit or only sometimes

### 3. What the user was doing
- The exact action that triggered it (clicked a button, refreshed, etc.)
- Whether the same action used to work and recently broke

### 4. What they expected
- What should have happened instead
- A description or screenshot of the working state if they have one

### 5. What changed recently
- Files edited in the last hour
- Packages installed or removed
- Environment variables added or changed
- Whether they pulled new code from git

## The error report template

Give the user this template to fill in:

```
ERROR: [paste the full error and stack trace]

WHERE: [URL, file, or component name]

WHAT I DID: [the exact action that triggered it]

WHAT I EXPECTED: [what should have happened]

RECENT CHANGES: [edited / installed / changed in the last hour]

TECH STACK: [framework + version, e.g. Next.js 15 App Router, Tailwind v4]
```

## The "blank page" rescue

A blank page is the scariest error because there's no message. The fix sequence:

1. **Open the browser console** (F12 → Console tab). 99% of blank pages have a red error there.
2. **Open the Network tab** and reload. Look for any failed (red) requests.
3. **Check the terminal** running the dev server. Look for compile errors.
4. **Hard refresh** (Cmd+Shift+R / Ctrl+Shift+R) to bypass cache.
5. **Check the URL** — typo? wrong port?

If all five are clean and the page is still blank, the issue is usually a
hydration mismatch or an unhandled promise in a server component.

## The "AI keeps breaking it" rescue

When the AI is making the same mistake repeatedly:

1. **Stop and revert.** Undo all of the AI's changes since the last working state. Use `git stash` or `git checkout .` if needed.
2. **Describe the original problem in one fresh message.** Do not include any of the failed attempts.
3. **Add an explicit constraint.** "Only modify the X component. Do not touch Y or Z."
4. **Ask for the smallest possible fix.** "Just make the button render — we'll add the click handler in a separate step."

Signs you should start a fresh conversation entirely:
- Same wrong answer 3 times in a row (the context is polluted)
- 50+ messages deep (the AI is forgetting earlier context)
- You changed direction mid-conversation
- You manually edited files and the AI is now working from a stale picture

## Common error patterns and their fixes

### `TypeError: Cannot read properties of undefined (reading 'X')`
The code is accessing a property on something that hasn't loaded yet.
**Fix:** Add a null check (`user?.name`), use optional chaining throughout, or add a loading state.

### `Hydration failed because the server rendered HTML didn't match the client`
A server component and client component rendered different content.
**Common causes:**
- `Date.now()`, `Math.random()`, or browser-only APIs in a component that runs on both
- Using `window` or `localStorage` without checking for client side
- Different content based on user locale or timezone
**Fix:** Wrap the dynamic part in `useEffect`, OR use the `suppressHydrationWarning` prop, OR mark the component as `"use client"`.

### `Module not found: Can't resolve 'X'`
The package isn't installed.
**Fix:** `npm install X`. Or the import path is wrong — check capitalization and the exact filename.

### `429 Too Many Requests` / `Rate limit exceeded`
You're hitting an API too fast.
**Fix:** Add debouncing (`use-debounce` package), caching, or exponential backoff.

### `CORS error`
The browser blocked a cross-origin request.
**Fix:** Configure CORS on the server, OR proxy the request through your own API route handler.

### `Unauthorized` / `401`
The auth token is missing or expired.
**Fix:** Ensure the request includes the auth header, OR refresh the token, OR re-sign-in.

### `Maximum update depth exceeded` (React)
A useEffect is updating state that triggers itself in an infinite loop.
**Fix:** Check the useEffect dependency array — you probably have a function or object that gets recreated on every render. Wrap it in `useCallback` or `useMemo`.

### `Cannot find module '@/...'`
Path alias not configured.
**Fix:** Check `tsconfig.json` has `"paths": { "@/*": ["./src/*"] }` and `baseUrl` is set.

### `PrismaClientInitializationError` / Drizzle connection errors
Database connection failing.
**Fix:** Check `DATABASE_URL` env var. In production, ensure connection pooling is configured (Supabase pooler, Neon pooler, or Prisma Accelerate).

### Supabase query returns empty array but rows clearly exist
Row Level Security is silently filtering them out. RLS doesn't error — it
just hides rows the current user can't read, so your query returns `[]`
instead of throwing.
**Fix:** Three things to check, in order:
1. **Is RLS enabled on the table?** Supabase dashboard → Table Editor →
   click table → "Enable RLS" toggle. If disabled, anon key reads everything.
   If enabled, the next checks matter.
2. **Is there a SELECT policy?** Authentication → Policies → click the table.
   Without a policy that returns `true` for the current user, every read
   returns nothing. Write a permissive policy like
   `USING (true)` for public tables, or `USING (auth.uid() = user_id)` for
   user-owned data.
3. **Are you using the right key?** The anon key is restricted by RLS; the
   service role key bypasses it. For server-side reads of public data, the
   anon key + a permissive policy is correct. For admin actions, use the
   service role key (server-only, NEVER in client code).

Quick test: temporarily run the same query in the Supabase SQL editor with
a `SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claim.sub" = 'user-id';`
to simulate the user. If it returns rows there but not in your app, your
client is using the wrong key.

### Page shows stale data after a database update
ISR is doing exactly what you told it to: serving the cached version.
**Fix:** After any mutation that should update a cached page, call
`revalidatePath` or `revalidateTag` from your server action / route handler:
```ts
import { revalidatePath } from "next/cache";

export async function updateProduct(id: string, data: ProductInput) {
  await supabase.from("products").update(data).eq("id", id);
  revalidatePath("/products");           // listing
  revalidatePath(`/products/${data.slug}`); // detail
  revalidatePath("/");                   // homepage if relevant
}
```
If the page is wrapped in a layout with `revalidate = 1800`, your option
is to either lower the timer (less stale, more rebuilds) OR call
`revalidatePath` explicitly after every mutation. The latter is faster
and cheaper.

If you're sure you called `revalidatePath` and it's STILL stale: check that
the path string matches exactly (no trailing slash, no `?` query string,
case-sensitive). The path matcher is literal, not a glob.

### Service worker is serving stale assets / blank page after deploy
The browser cached the old service worker, which cached old chunks, which
404 because the chunk filenames changed in the new build. Result: blank
page or "ChunkLoadError" in the console.
**Fix:**
1. **Bump the service worker version**. In `public/sw.js`, change a
   `CACHE_VERSION` constant — it triggers a new install on next visit.
2. **Force-update on deploy**. In your service worker:
   ```js
   self.addEventListener("install", (e) => self.skipWaiting());
   self.addEventListener("activate", (e) => e.waitUntil(self.clients.claim()));
   ```
3. **Exclude API routes from caching**. The most common bug: caching
   `/api/*` so users see stale data forever. The fix is in your service
   worker's fetch handler:
   ```js
   self.addEventListener("fetch", (event) => {
     const url = new URL(event.request.url);
     // Never cache API or external auth/AI endpoints
     if (url.pathname.startsWith("/api/")) return;
     if (url.hostname.includes("supabase.co")) return;
     if (url.hostname.includes("openai.com")) return;
     // ... rest of caching logic
   });
   ```
4. **Tell users to hard-reload** when in doubt: Cmd+Shift+R / Ctrl+Shift+R
   bypasses the service worker entirely.

If you're just debugging locally, open DevTools → Application → Service
Workers → "Update on reload" + "Bypass for network". This makes the page
behave as if there's no service worker.

## Browser DevTools mastery

Every vibe coder should know these five DevTools tabs cold:

- **Console** — JavaScript errors and `console.log` output. Filter by Error/Warning level.
- **Network** — every request the page makes. Click any row to see the request, response, and timing. Filter by Fetch/XHR for API calls.
- **Elements** — inspect the DOM and computed CSS. Right-click anything → Inspect. Toggle CSS properties live.
- **Sources** — set breakpoints in your code. Debugger statement (`debugger;`) drops you here.
- **Application** — cookies, localStorage, service workers, indexedDB.

## Console.log strategy

Don't sprinkle `console.log` everywhere. Be deliberate:

```js
// Tag your logs so you can find them
console.log("[checkout]", { user, cart });

// Use console.table for arrays of objects
console.table(users);

// Group related logs
console.group("API call");
console.log("Request:", req);
console.log("Response:", res);
console.groupEnd();

// console.error gets a stack trace
console.error("Failed to fetch user:", err);

// Conditional logging
if (process.env.NODE_ENV !== "production") {
  console.log("debug:", value);
}
```

When the bug is fixed, **remove the logs**. Don't ship them to production.

## When to reach for production debugging tools

For bugs that only appear in production, install **Sentry**:

```bash
npx @sentry/wizard@latest -i nextjs
```

You get:
- Every error with full stack trace
- The user session that triggered it
- Browser, OS, screen size
- A replay of the user's actions before the crash

`console.log` is for local debugging. Sentry is for production debugging.

## What good debugging looks like

- Every error in the console is investigated, not ignored
- The user can describe the error in one sentence using the template
- The fix is targeted to the actual cause, not a guess
- After the fix, you confirm the original action now works AND nothing else broke
- Production errors flow to Sentry, not silently disappear
- You write a one-line commit message that names the bug

## Common mistakes to avoid

- **Guessing the cause.** Always verify with the actual error and stack trace.
- **Trying random fixes.** Each random fix adds noise. Stop and gather context.
- **Ignoring the console.** It almost always has the answer.
- **"It works on my machine."** Get the user's exact error, not your assumption.
- **Fixing the symptom, not the cause.** A try/catch that swallows the error is not a fix — it's a hiding place.
- **Console.log everywhere.** Be deliberate. Tag your logs. Remove them after.
- **Skipping the reproduction step.** If you can't reproduce a bug, you can't fix it. Get steps from the user.
- **Not using git bisect.** When a bug appeared "recently", `git bisect` finds the exact commit that broke it.
- **Asking AI to "just fix it" without context.** AI can't fix what it can't see. Show the error AND the relevant code.

## Going deeper
- Debugging: https://www.codebooks.ai/debugging
- When Things Break: https://www.codebooks.ai/when-things-break
- Error Monitoring: https://www.codebooks.ai/error-monitoring


---

This skill is part of the **CodeBooks Vibe Coding Skills Library**.
Browse all skills, install guides, and the source chapters at
https://www.codebooks.ai/skills
