| name | systematic-debugging |
| description | Use when encountering any bug, test failure, or unexpected behavior. 4-phase root cause process that prevents fix-first-think-later mistakes. Covers Strapi API errors, Next.js hydration mismatches, cache staleness, and enrollment state bugs. |
| invocation | auto |
Systematic Debugging for Odyssey
STOP — Do Not Fix Anything Yet
Before writing a single line of fix code, complete all 4 phases. Premature fixes are the #1 cause of recurring bugs and wasted tokens.
Phase 1: Reproduce & Observe
Goal: See the bug happen. Understand exactly what occurs vs. what should occur.
-
Get the exact error. Read the error message, stack trace, or test failure output completely. Note the file, line number, and error type.
-
Reproduce it. Run the failing command:
cd frontend && npx jest path/to/failing.test.ts
cd frontend && npm run dev
cd frontend && npm run build
-
Document what you see:
- Expected behavior: [what should happen]
- Actual behavior: [what actually happens]
- Error message: [exact text]
Phase 2: Trace the Root Cause
Goal: Find WHERE and WHY the bug originates — not just where it manifests.
Strategy: Follow the data flow backward from the error.
For Strapi/API errors:
- Read the request function in
lib/requests/ that fetches this data
- Check the Strapi schema:
backend/src/api/{type}/content-types/{type}/schema.json
- Check if
fetchAPI() auto-flatten is handling the response correctly
- Check cache tags — is stale data being served? (
CACHE_TAGS in the request, revalidateTag() in the action)
- Check if a Server Action is using raw
fetch() without flattenAttributes()
For Next.js hydration mismatches:
- Check if a Server Component is accidentally using browser APIs
- Check if
"use client" is missing on an interactive component
- Check for conditional rendering that differs server-side vs. client-side (e.g.,
window.innerWidth)
- Check for Zustand stores being read during SSR
For test failures:
- Read the test — understand what it expects
- Read the implementation — understand what it does
- Check mocks — is
fetchAPI mocked correctly? Is the mock returning flat data (not nested Strapi format)?
- Check imports — is the test importing from the right path? (
@/lib/utils vs relative)
For cache/stale data issues:
- Check
CACHE_TAGS on the request function — is the right tag used?
- Check the Server Action — does it call
revalidateTag() with the matching tag?
- Check for the
cache/next mutual exclusion bug — are both passed to fetchAPI()?
- Check per-user tags — is
CACHE_TAGS.enrollments(userId) being invalidated, not just CACHE_TAGS.allEnrollments?
For enrollment state bugs:
- Read the enrollment schema:
backend/src/api/enrollment/content-types/enrollment/schema.json
- Check
enrollment-populates.ts — is the query populating the right relations?
- Check nullable fields:
rating is nullable, viewedLessons can be empty
- Check the
droplet-lesson join table for orderIndex — lesson ordering bugs often originate here
Phase 3: Verify Your Theory
Goal: Confirm the root cause before writing a fix.
Ask yourself:
- "Does my theory explain ALL symptoms, not just some?"
- "If I'm right, what other observable effects would exist?" Then check for them.
- "Could there be a simpler explanation?"
Verification techniques:
- Add a temporary
console.log() at the suspected origin point
- Check
git log -p path/to/file — did a recent change introduce this?
- Search for similar patterns:
Grep for the same function/pattern elsewhere — is it broken there too?
Phase 4: Fix with Precision
Goal: Minimal, targeted fix at the root cause — not the symptom.
- Fix the root cause, not the symptom location
- Write or update a test that would have caught this bug
- Run the test:
cd frontend && npx jest path/to/test.ts
- Run related tests to check for regressions
- Verify the fix doesn't break the
cache/next mutual exclusion, missing flattenAttributes(), or missing revalidateTag() patterns
Anti-Patterns
- Shotgun debugging: Changing multiple things at once to "see what sticks." Change ONE thing, verify, repeat.
- Fix-and-pray: Writing a fix without understanding the root cause. The bug will return.
- Googling the error message first: Read the code first. Most bugs are in our code, not the framework.
- Ignoring the test: If there's a test for this behavior, the test defines correctness. If the test is wrong, fix the test first.