Debugging Workflow (/debug-issue)¶
In plain terms
A structured way to track down the real cause of a bug and confirm the fix actually worked.
What this skill does
Structured root-cause analysis and fix verification for bugs and errors
[!TIP] Use when the user reports an error, 500 status code, or "something is broken."
Trigger Conditions¶
- Keywords: "Fix", "Debug", "Error", "Broken", "Doesn't work", "500", "404"
- Context: Any specific bug report
0. Early Context Gathering (DO THIS FIRST)¶
[!IMPORTANT] Before diving into code, gather critical context to avoid wasting time in the wrong area.
- [ ] Ask for screenshot if UI-related — identifies the exact component/view
- [ ] Ask "what works vs what doesn't" — if field A works but field B doesn't, this points to field-specific logic
- [ ] Identify the exact view/component name — "Pipeline view" vs "Company Directory" vs "Opportunity modal"
- [ ] Check browser console — ask if there are errors or logs visible
1. Discovery & Reproduction¶
- [ ] Logs: Check backend console logs and browser console/network tab
- [ ] Trace: Locate the exact file and line where the error originates
- [ ] State: Identify the data state (inputs/DB state) that causes the crash
2. Root Cause Analysis¶
- [ ] Validation: Is it a Pydantic validation error? Check
backend/app/schemas/ - [ ] Permissions: Is it an auth issue? Check
require_auth/require_permissiondependencies and Supabase RLS policies - [ ] Database: Is the SQLModel query correct? Check
backend/app/api/*.pyandbackend/app/models/*.py - [ ] Error handling: Is
HTTPExceptionbeing raised with the correct status code? Check FastAPI exception handlers - [ ] Logic: Is it a null/undefined or incorrect business logic?
- [ ] Regression: Did a recent migration or refactor cause this?
3. Proposal & Fix¶
- [ ] Plan: Explain the root cause to the user before fixing
- [ ] Implementation: Apply the fix carefully
- [ ] Cleanup: Remove all
console.log/print()debug statements added during discovery
4. Verification¶
- [ ] Test: Verify the specific case that was failing
- [ ] Side Effects: Ensure the fix doesn't break related functionality
- [ ] Architecture: Run
/architecture-reviewif the fix involved UI or API changes
E2E Test Failure Investigation¶
[!TIP] Use when a Playwright test fails. The HTML report at
frontend/playwright-report/index.htmlis the primary diagnostic tool.
How to Investigate a Failed E2E Test¶
- Read the Playwright HTML report — open
frontend/playwright-report/index.htmlin browser, or runcd frontend && npx playwright show-reportto serve it locally. -
Each failed test shows: error message, failing step, screenshot (if taken), and trace file.
-
Identify failure category:
| Symptom | Likely Cause |
|---|---|
locator.click: Element not found |
Missing or changed data-testid, component not rendered |
Expected: visible / Received: hidden |
Async state not resolved, wrong waitFor condition |
strict mode violation: resolved to N elements |
Selector matches too many elements, needs more specificity |
Page crashed / network error |
Backend not running, API returned error |
Timeout 30000ms exceeded |
Backend slow/unavailable, login failed silently |
Expected URL to match |
Route changed, redirect not happening |
| Test passes alone but fails in suite | Race condition (shared backend state), use test.describe.configure({ mode: "serial" }) |
-
Check the source component for the relevant
data-testidattribute. If missing, add it. -
Check backend logs for 4xx/5xx errors during the test run — these often cause UI flows to fail silently.
-
Run the single failing spec to confirm the fix:
cd frontend && npx playwright test e2e/<spec>.spec.ts --reporter=line -
Re-run the full suite after fixing to confirm no regressions:
cd frontend && npx playwright test --reporter=line
E2E Conventions to Enforce¶
- All selectors must use
data-testid— never CSS classes, element types, or text content (exceptgetByRolefor dialogs/headings) - Test data created during a test must be cleaned up in
afterEachvia API call - Tests that share backend state must run in
serialmode viatest.describe.configure({ mode: "serial" }) - Login is handled by
e2e/helpers/auth.ts— never duplicate login logic in individual specs
Known Issue Patterns¶
Form Dirty State Not Triggering¶
Symptoms: "Save button stays disabled when editing field X, but works for field Y"
Common Causes (check in order):
1. Field-whitelist hooks: Check the relevant dirty-state hook — it only checks fields explicitly listed. If a field isn't in the tracked fields array, it won't trigger dirty state.
2. Shared object references: Initial state and current state point to the same object. Fix with structuredClone().
3. Wrong component/view: User may be on a different screen than assumed.
Quick Debug:
console.log('Initial:', JSON.stringify(initialState?.fieldName));
console.log('Current:', JSON.stringify(currentState?.fieldName));
console.log('isDirty:', isDirty);
API / Backend Errors¶
- Check
backend/app/api/*.pyfor the endpoint logic - Check
backend/app/models/*.pyfor model definitions - Verify all error paths raise
HTTPExceptionwith proper status codes - Check Supabase RLS policies if getting 403/401
UI Component Not Updating¶
- Check if state is being set with a new reference (not mutating existing object)
- Check if
useMemo/useCallbackdependencies are correct - Check if the parent component is re-rendering
Race Conditions / Causal Order Failures¶
Symptoms: "Modals closing too fast", "Intermittent 500 errors", "Deleted items reappearing briefly", "Notifications missing"
Common Causes:
1. Unawaited backend tasks: Side effects (notifications, logs) in routes not awaited before response
2. Eager frontend closure: Modals close on click instead of after await deleteMutation.mutateAsync()
3. Missing mutateAsync: Using mutate (fire-and-forget) instead of mutateAsync
Quick Debug:
- Backend: verify async operations are properly awaited before returning response
- Frontend: verify mutateAsync is awaited before closing modals
- Check for sequence markers in console logs
[!TIP] Use when the user reports an error, 500 status code, or "something is broken."
Trigger Conditions¶
- Keywords: "Fix", "Debug", "Error", "Broken", "Doesn't work", "500", "404"
- Context: Any specific bug report
0. Early Context Gathering (DO THIS FIRST)¶
[!IMPORTANT] Before diving into code, gather critical context to avoid wasting time in the wrong area.
- [ ] Ask for screenshot if UI-related — identifies the exact component/view
- [ ] Ask "what works vs what doesn't" — if field A works but field B doesn't, this points to field-specific logic
- [ ] Identify the exact view/component name — "Pipeline view" vs "Company Directory" vs "Opportunity modal"
- [ ] Check browser console — ask if there are errors or logs visible
1. Discovery & Reproduction¶
- [ ] Logs: Check backend console logs and browser console/network tab
- [ ] Trace: Locate the exact file and line where the error originates
- [ ] State: Identify the data state (inputs/DB state) that causes the crash
2. Root Cause Analysis¶
- [ ] Validation: Is it a Zod validation error? Check
backend/src/validation/ - [ ] Permissions: Is it an auth issue? Check
authenticatemiddleware andbackend/src/middleware/auth.ts - [ ] Database: Is the Drizzle query correct? Check
backend/src/routes/*.tsandbackend/src/db/schema.ts - [ ] Error routing: Is
next(error)being called? Checkbackend/src/middleware/errorHandler.ts - [ ] Logic: Is it a null/undefined or incorrect business logic?
- [ ] Regression: Did a recent migration or refactor cause this?
3. Proposal & Fix¶
- [ ] Plan: Explain the root cause to the user before fixing
- [ ] Implementation: Apply the fix carefully
- [ ] Cleanup: Remove all
console.logdebug statements added during discovery
4. Verification¶
- [ ] Test: Verify the specific case that was failing
- [ ] Side Effects: Ensure the fix doesn't break related functionality
- [ ] Architecture: Run
/architecture-reviewif the fix involved UI or API changes
Known Issue Patterns¶
Form Dirty State Not Triggering¶
Symptoms: "Save button stays disabled when editing field X, but works for field Y"
Common Causes (check in order):
1. Field-whitelist hooks: hooks/useOpportunityFormDirty.ts only checks fields explicitly listed in opportunityRecordFields. If a field isn't there, it won't trigger dirty state.
2. Shared object references: Initial state and current state point to the same object. Fix with structuredClone().
3. Wrong component/view: User may be on a different screen than assumed.
Quick Debug:
console.log('Initial:', JSON.stringify(initialState?.fieldName));
console.log('Current:', JSON.stringify(currentState?.fieldName));
console.log('isDirty:', isDirty);
API / Backend Errors¶
- Check
backend/src/routes/*.tsfor the endpoint logic - Check
backend/src/db/schema.tsfor column definitions - Verify all catch blocks use
next(error), not manualres.status(500) - Check Supabase RLS policies if getting 403/401
S3 / File Upload Errors¶
- Check
backend/src/services/s3Service.tsfor S3 operation logic - Check
backend/src/routes/documentRoutes.tsfor the upload route - Verify AWS credentials in
.env(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_S3_BUCKET) - Check multer middleware configuration for file size/type limits
UI Component Not Updating¶
- Check if state is being set with a new reference (not mutating existing object)
- Check if
useMemo/useCallbackdependencies are correct - Check if the parent component is re-rendering
Race Conditions / Causal Order Failures¶
Symptoms: "Modals closing too fast", "Intermittent 500 errors", "Deleted items reappearing briefly", "Notifications missing"
Common Causes:
1. Unawaited backend tasks: Side effects (notifications, logs) in routes not awaited before res.json()
2. Eager frontend closure: Modals close on click instead of after await deleteMutation.mutateAsync()
3. Missing mutateAsync: Using mutate (fire-and-forget) instead of mutateAsync
Quick Debug:
- Backend: verify Promise.allSettled() is used for concurrent side effects
- Frontend: verify mutateAsync is awaited before closing modals
- Check for sequence markers in console: [DELETE][1/3], [DELETE][2/3]