Skip to content

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_permission dependencies and Supabase RLS policies
  • [ ] Database: Is the SQLModel query correct? Check backend/app/api/*.py and backend/app/models/*.py
  • [ ] Error handling: Is HTTPException being 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-review if 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.html is the primary diagnostic tool.

How to Investigate a Failed E2E Test

  1. Read the Playwright HTML report — open frontend/playwright-report/index.html in browser, or run cd frontend && npx playwright show-report to serve it locally.
  2. Each failed test shows: error message, failing step, screenshot (if taken), and trace file.

  3. 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" })
  1. Check the source component for the relevant data-testid attribute. If missing, add it.

  2. Check backend logs for 4xx/5xx errors during the test run — these often cause UI flows to fail silently.

  3. Run the single failing spec to confirm the fix:

    cd frontend && npx playwright test e2e/<spec>.spec.ts --reporter=line
    

  4. 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 (except getByRole for dialogs/headings)
  • Test data created during a test must be cleaned up in afterEach via API call
  • Tests that share backend state must run in serial mode via test.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/*.py for the endpoint logic
  • Check backend/app/models/*.py for model definitions
  • Verify all error paths raise HTTPException with 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/useCallback dependencies 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 authenticate middleware and backend/src/middleware/auth.ts
  • [ ] Database: Is the Drizzle query correct? Check backend/src/routes/*.ts and backend/src/db/schema.ts
  • [ ] Error routing: Is next(error) being called? Check backend/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.log 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-review if 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/*.ts for the endpoint logic
  • Check backend/src/db/schema.ts for column definitions
  • Verify all catch blocks use next(error), not manual res.status(500)
  • Check Supabase RLS policies if getting 403/401

S3 / File Upload Errors

  • Check backend/src/services/s3Service.ts for S3 operation logic
  • Check backend/src/routes/documentRoutes.ts for 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/useCallback dependencies 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]