# Systematic Debugging with AI Agents

> A disciplined methodology for finding and fixing bugs with AI assistance. Address root causes, not symptoms.

## When to Use

- Service returns unexpected errors
- Tests fail after changes
- Build breaks in CI or Docker
- Runtime crashes in production or staging

## The Pattern

Debugging with AI agents follows a **5-phase cycle**:

```
1. REPRODUCE  →  2. LOCATE  →  3. DIAGNOSE  →  4. FIX  →  5. VERIFY
     ↑                                                         │
     └─────────────── (if fix doesn't hold) ──────────────────┘
```

The critical discipline: **never skip from REPRODUCE to FIX**. The LOCATE and DIAGNOSE phases are where agents add the most value.

## Phase 1: Reproduce

Before asking the agent to fix anything, establish the failure:

```
The /api/users endpoint returns 500.
Steps to reproduce:
  1. POST /api/auth/login with valid credentials
  2. GET /api/users with the returned token
  3. Response: 500 Internal Server Error

Expected: 200 with user list
Error in logs: "Cannot read property 'email' of undefined"
```

**Give the agent:**

- The exact error message
- Steps to reproduce
- What you expected vs. what happened
- Relevant log output

## Phase 2: Locate

Ask the agent to find the source, not fix it yet:

```
Find where this error originates. Check:
1. The route handler in src/modules/users/routes.ts
2. The repository method it calls
3. The Cosmos query being executed
Don't fix anything yet — just tell me where the bug is.
```

**Agent tools for locating:**

- `grep_search` for error messages
- `read_file` to inspect suspected files
- `code_search` for semantic search across codebase

## Phase 3: Diagnose

Once located, understand the root cause:

```
The error is on line 42 of repository.ts.
Why is `user.email` undefined here?
Is the Cosmos query returning the wrong shape?
Is the partition key wrong?
```

**Common root causes from real sessions:**

| Symptom                       | Root Cause                                                 | Pattern                                                         |
| ----------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------- |
| tsc errors in Docker build    | Vitest globals (`describe`, `beforeAll`) in non-test files | Add `"src/**/*.test.ts"` to tsconfig `exclude`                  |
| Missing file in Xcode build   | File not added to PBXSourcesBuildPhase                     | Check pbxproj ↔ filesystem consistency                          |
| 500 on API endpoint           | Cosmos query returns different shape than expected         | `SELECT VALUE COUNT(1)` returns `number`, not `{count: number}` |
| Next.js build fails           | `useSearchParams()` without `<Suspense>` boundary          | Next.js 16 requires Suspense for client hooks in static pages   |
| Import errors in ESM          | Missing `.js` extension in import path                     | ESM requires explicit extensions                                |
| Docker build fails on Next.js | Env vars undefined during build-time data collection       | Add dummy build-time env vars in Dockerfile                     |

## Phase 4: Fix

Now fix with the **minimum viable change**:

```
Fix the Cosmos query in repository.ts:42.
The query uses SELECT VALUE COUNT(1) which returns a number directly,
not {count: number}. Change the type annotation from
`{count: number}` to `number`.

Minimal fix only — don't refactor anything else.
```

**Fix principles:**

- **One-line fix when possible** — Don't refactor while debugging
- **Fix upstream, not downstream** — Fix the query, not the consumer
- **No workarounds** — `|| 0` is a workaround; fixing the type is the fix
- **Don't weaken types** — Adding `any` to silence an error is not a fix

## Phase 5: Verify

Always verify the fix:

```
Run these commands to verify:
1. npm test — all tests pass
2. npm run typecheck — no type errors
3. Manual test: POST /api/auth/login, then GET /api/users
```

**If verification fails, go back to Phase 1 with new information.**

## Debugging Checklists by Domain

### API endpoint returns wrong status/data

1. Read the route handler
2. Check input validation (Zod schema)
3. Check auth middleware (is user attached to request?)
4. Check repository method (query, partition key)
5. Check error handler (is it swallowing errors?)

### Build fails

1. Read the exact error message (first error, not cascading ones)
2. Check if it's a type error (tsc), lint error, or runtime error
3. Check recent changes (`git diff HEAD~3`)
4. Check if test files are leaking into production build

### Docker build fails

1. Check Dockerfile stages (is `npm ci` running correctly?)
2. Check if build-time env vars are needed (Next.js data collection)
3. Check if test files are excluded from tsc
4. Check `.dockerignore` (is node_modules excluded?)

### iOS/Xcode build fails

1. Check if new files are in the correct target's Build Phases
2. Check if pbxproj references match filesystem
3. Check signing/provisioning
4. Clean build folder (Cmd+Shift+K) and retry

### Tests fail after changes

1. Read the failing test assertion
2. Check if the test expects old behavior (update test)
3. Check if the implementation broke a contract (fix implementation)
4. **Never modify tests to make them pass — fix the code**

## Anti-Patterns

- **Shotgun debugging** — "Try changing X, Y, and Z" without understanding why
- **Workaround fixes** — `try { } catch { return null }` masks the real bug
- **Weakening types** — `as any` or removing type annotations
- **Fixing tests instead of code** — "The test expected 5 but got 3, so change the test to expect 3"
- **Skipping verification** — Assuming the fix works without running tests

## Real-World Example

**Bug:** TestFlight build crashed on launch.

**Phase 1 (Reproduce):** Build uploaded successfully but app crashes immediately.

**Phase 2 (Locate):** Crash log shows missing symbol in LysnrKeyboard target. File `LysnrTelemetry.swift` is referenced but not compiled.

**Phase 3 (Diagnose):** The Swift file exists on disk but was never added to the keyboard extension's PBXSourcesBuildPhase in the Xcode project. The main app target has it, but the keyboard target doesn't.

**Phase 4 (Fix):** Added the file reference to the keyboard target's compile sources. One-line pbxproj change.

**Phase 5 (Verify):** `xcodebuild -scheme LysnrKeyboard build` — succeeds. Created `/mobile-code-quality` workflow with pbxproj consistency check to prevent recurrence.

**Meta-fix:** Added pre-flight compile check to `/release-testflight` workflow so this class of bug can never ship again.

## Related Skills

- [08 — Test-Driven Bug Fixing](./08-test-driven-fixing.md)
- [12 — Pre-Release Quality Gates](./12-quality-gates.md)
- [05 — Effective Agent Prompting](./05-effective-agent-prompting.md)
