Back to List

The Complete Claude Code Debugging Workflow Guide: AI-Powered Error Diagnosis

2026-03-12·10 min read·AITutorial

Introduction

Debugging is where developers spend the most time — and often the most frustrating time. Throughout this series, we've touched on debugging in various contexts: the Testing Guide covered debugging failing tests (Section 6), the Git Guide introduced git bisect for tracking down regressions, and the Prompt Guide showed how to describe bugs effectively. But we've never systematically covered: how to build a complete debugging workflow with Claude Code.

This article is the dedicated debugging guide. We'll cover everything from interpreting cryptic error messages to diagnosing race conditions, from performance profiling to production incident response. If the Refactoring Guide is about improving code that works, this guide is about fixing code that doesn't.

Let's turn debugging from a frustrating guessing game into a systematic, AI-assisted process.

1. Why Debugging Needs an AI Partner

The Debugging Pain Points

Pain PointDescriptionTime Cost
Cryptic error messagesStack traces pointing to compiled code, not your source15-30 min
Scope uncertainty"Is it the frontend, backend, or database?"30-60 min
Intermittent bugsWorks 9 out of 10 times, fails unpredictably1-4 hours
Environment differences"Works on my machine"30-90 min
Dependency conflictsVersion mismatches across packages30-60 min
Knowledge gapsUnfamiliar library internals or APIs1-2 hours

Manual Debugging vs Claude Code Debugging

DimensionManual DebuggingClaude Code Debugging
Error interpretationGoogle the error, scan Stack OverflowInstant contextual explanation with your codebase
Scope narrowingAdd console.logs, restart, repeatAnalyzes call chain and narrows scope in seconds
Hypothesis testingManually edit code, test, revertSuggests multiple hypotheses ranked by likelihood
Fix generationWrite fix, hope it worksGenerates fix with explanation of why it works
Regression preventionMaybe write a test afterwardAutomatically suggests regression test
Knowledge retentionLives in your head (or a forgotten wiki)Encoded in CLAUDE.md and test suites

The Debugging Mental Model

Effective debugging follows a consistent pattern, whether you're using AI or not:

Hypothesis → Verify → Narrow Scope → Repeat

Claude Code accelerates every step:

# Step 1: Hypothesis — describe the symptom
> The checkout page shows a blank screen after clicking "Place Order".
> No errors in the console. Network tab shows a 200 response.
 
# Step 2: Verify — Claude Code suggests checks
# Check error boundaries, silent promise rejections, conditional null renders
 
# Step 3: Narrow — provide more context
> The response body from /api/checkout is: {"status": "success", "orderId": null}
> orderId is null — could that be the issue?
 
# Step 4: Claude Code traces orderId through your components

The key insight: describe symptoms accurately and let Claude Code generate hypotheses.

2. Error Message Interpretation

Error Type Classification

Error TypeExampleTypical CauseClaude Code Approach
SyntaxUnexpected token '}'Missing bracket, typoReads file, finds exact location
RuntimeTypeError: Cannot read properties of undefinedNull reference, missing dataTraces variable origin through call chain
TypeType 'string' is not assignable to type 'number'Type mismatchAnalyzes type definitions and suggests fix
NetworkERR_CONNECTION_REFUSEDServer down, wrong portChecks server config, port bindings
PermissionEACCES: permission deniedFile/port permission issueChecks file ownership, suggests chmod/sudo
ModuleCannot find module './utils'Wrong path, missing exportVerifies file existence, checks tsconfig paths

Stack Trace Reading Techniques

Stack traces are the first clue. Feed them to Claude Code with context:

# Bad: just the error message
> TypeError: Cannot read properties of undefined (reading 'map')
 
# Good: full stack trace + what changed recently
> I'm getting this error when loading the blog list page:
> TypeError: Cannot read properties of undefined (reading 'map')
>     at BlogList (app/[locale]/blog/page.tsx:42:18)
>     at renderWithHooks (node_modules/react-dom/...)
> The page was working yesterday. I recently changed getPostsByLocale
> to add tag filtering.

Claude Code uses the stack trace to open the file at line 42, trace what .map() is called on, check getPostsByLocale for recent changes, and find the bug (likely the tag filter returns undefined instead of []).

Prompt Templates for Error Interpretation

# Unknown error
> I'm seeing this error and I don't understand it: [paste full error + stack trace]
 
# Error with context
> This error started after [describe recent change]: [paste error]
 
# Intermittent error
> This error happens about 30% of the time: [paste error]
> What could cause it to be intermittent?

Error Message → Root Cause Mapping

Error MessageLikely Root CauseFirst Thing to Check
Cannot read properties of undefinedVariable not initialized or async data not loadedCheck if data is fetched before render
Hydration failedServer/client HTML mismatchCheck for browser-only APIs in SSR
Module not foundWrong import path or missing dependencyCheck file path and package.json
CORS errorBackend missing CORS headersCheck API server CORS configuration
ENOMEMOut of memoryCheck for memory leaks, increase Node heap
ECONNREFUSEDTarget service not runningCheck if the server/database is up
Maximum call stack size exceededInfinite recursionCheck for circular function calls
ETIMEOUTNetwork or DNS issueCheck connectivity, DNS resolution

For more on writing effective prompts when describing bugs, see the Prompt Techniques Guide.

3. Root Cause Analysis

Binary Search Debugging

When you know a feature used to work but now it doesn't, binary search is the fastest approach.

Code-level binary search — comment out half the code:

> The dashboard page is crashing. It has 8 widgets.
> Comment out the bottom 4 widgets to check if the bug is in the top or bottom half.

If the crash stops, the bug is in the bottom half. Repeat until you find the culprit.

Git-level binary searchgit bisect:

git bisect start
git bisect bad          # current commit is broken
git bisect good abc123  # this older commit was working
# Git checks out a middle commit — test it, then: git bisect good/bad
# Repeat until git identifies the exact commit

git bisect + Claude Code Automation

> The blog search is broken — it was working at commit a1b2c3d.
> Use git bisect with `npm test -- --run search` to find the bad commit.
git bisect start && git bisect bad HEAD && git bisect good a1b2c3d
git bisect run npm test -- --run search

This automatically tests each bisect step. For more on git bisect, see the Git Workflow Guide.

Log Analysis Strategy

When the error isn't obvious, structured log analysis helps:

> Here are the last 50 lines of the server log when the error occurs:
> [paste logs]
> Identify the first error, any timing patterns, and which module is failing.

Dependency Tracking

> Something broke after running npm update yesterday.
> Compare package-lock.json with the previous version and identify
> which packages changed. Check changelogs for breaking changes.

4. Runtime Debugging

console.log Strategy

Not all console.logs are created equal. Ask Claude Code for structured logging:

> Add debugging logs to the checkout flow. Use structured logging with
> timestamp, function name, sanitized parameters, and output/error.

Claude Code generates:

// lib/debug.ts
const DEBUG = process.env.NODE_ENV === 'development'
 
export function debugLog(
  fn: string,
  action: 'enter' | 'exit' | 'error',
  data?: Record<string, unknown>
) {
  if (!DEBUG) return
  const prefix = { enter: '→', exit: '←', error: '✗' }[action]
  console.log(`[${new Date().toISOString()}] ${prefix} ${fn}`, data ?? '')
}
 
// Usage
export async function processCheckout(cart: Cart, user: User) {
  debugLog('processCheckout', 'enter', { cartItems: cart.items.length, userId: user.id })
  try {
    const order = await createOrder(cart, user)
    debugLog('processCheckout', 'exit', { orderId: order.id })
    return order
  } catch (error) {
    debugLog('processCheckout', 'error', {
      message: error instanceof Error ? error.message : 'Unknown error',
    })
    throw error
  }
}

Conditional Logging

For bugs that only appear with specific data:

export async function fetchPosts(locale: string, tag?: string) {
  const posts = await getPostsByLocale(locale)
  const filtered = tag ? posts.filter(p => p.tags.includes(tag)) : posts
 
  // Only log when results are unexpectedly empty
  if (filtered.length === 0 && posts.length > 0) {
    console.warn('[fetchPosts] Filter returned 0 results', {
      locale, tag, totalPosts: posts.length,
      availableTags: [...new Set(posts.flatMap(p => p.tags))],
    })
  }
  return filtered
}

Network Request Debugging

> The dashboard is showing stale data. Add a fetch wrapper that logs all requests.
// lib/debug-fetch.ts
const originalFetch = globalThis.fetch
 
export function enableFetchDebugging() {
  globalThis.fetch = async (input, init) => {
    const url = typeof input === 'string' ? input : input.toString()
    console.group(`[Fetch] ${init?.method ?? 'GET'} ${url}`)
    try {
      const response = await originalFetch(input, init)
      console.log('Status:', response.status)
      console.groupEnd()
      return response
    } catch (error) {
      console.error('Fetch error:', error)
      console.groupEnd()
      throw error
    }
  }
}

State Tracking for React

> The shopping cart count doesn't update when I add items.
> Add state change logging to the CartProvider.
// context/CartContext.tsx
export function CartProvider({ children }: { children: React.ReactNode }) {
  const [cart, setCart] = useState<CartState>(initialState)
  const prevCartRef = useRef<CartState>(initialState)
 
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      const prev = prevCartRef.current
      console.log('[CartProvider] State changed:', {
        prevItems: prev.items.length,
        newItems: cart.items.length,
        added: cart.items.filter(i => !prev.items.find(p => p.id === i.id)),
        removed: prev.items.filter(i => !cart.items.find(c => c.id === i.id)),
      })
      prevCartRef.current = cart
    }
  }, [cart])
  // ... rest of provider
}

5. Async and Concurrency Issues

Async bugs are among the hardest to debug because they're often intermittent.

Race Condition Diagnosis

> Users sometimes see stale profile data after updating their profile.
> The update API returns 200, but the page still shows old data.
> This happens maybe 20% of the time. Could this be a race condition?
// Bug: race condition between update and refetch
async function handleProfileUpdate(data: ProfileData) {
  await updateProfile(data)
  router.refresh() // Doesn't wait for revalidation to complete
}
 
// Fix: optimistic update or wait for revalidation
async function handleProfileUpdate(data: ProfileData) {
  const updated = await updateProfile(data)
  setProfile(updated) // Optimistic update — immediate UI feedback
}

Promise Chain Debugging

Unhandled promise rejections are silent killers:

> Audit the API route handlers in app/api/ for unhandled promise rejections.
> Check for missing try/catch, .then() without .catch(), and un-awaited promises.
// Bug: fire-and-forget promise — errors silently swallowed
export async function POST(request: Request) {
  const data = await request.json()
  sendNotificationEmail(data.email, data.orderId) // Not awaited!
  return Response.json({ success: true })
}
 
// Fix: await or explicitly handle the error
export async function POST(request: Request) {
  const data = await request.json()
  sendNotificationEmail(data.email, data.orderId).catch(error => {
    console.error('[POST /api/order] Email failed:', error)
  })
  return Response.json({ success: true })
}

Event Loop Issues

> The server becomes unresponsive for 2-3 seconds every minute.
> CPU spikes to 100%. Could something be blocking the event loop?
// Bug: synchronous file read in a request handler
export async function GET(request: Request) {
  const data = fs.readFileSync('/path/to/large-file.json', 'utf-8') // Blocks!
  return Response.json(JSON.parse(data))
}
 
// Fix: use async version
export async function GET(request: Request) {
  const data = await fs.promises.readFile('/path/to/large-file.json', 'utf-8')
  return Response.json(JSON.parse(data))
}

Common Async Bug Patterns

Bug PatternSymptomRoot CauseFix
Missing awaitFunction returns Promise<T> instead of TForgot await on async callAdd await keyword
Race conditionIntermittent stale dataTwo async ops competeUse mutex, queue, or optimistic update
Unhandled rejectionSilent failure, no error logged.catch() missingAdd error handling
Event loop blockingServer freezes periodicallySync I/O in async contextUse async alternatives
Zombie listenersMemory leak, duplicate eventsEvent listener not cleaned upReturn cleanup in useEffect
Stale closureCallback uses outdated stateClosure captures old valueUse useRef or dependency array

6. Environment and Configuration Issues

"Works on my machine" bugs are real, common, and Claude Code can help systematically eliminate them.

"Works on My Machine" Troubleshooting Checklist

> The app works locally but fails in CI/staging with this error: [paste error]
> Walk me through the checklist: Node version? Env vars? Path case sensitivity?
> OS-specific behavior? Dependency version differences?

Environment Variable Issues

> The app crashes in production with "Invalid URL" but works locally.
> Check all environment variable usage and identify any that might
> be undefined in production.
// lib/env.ts — defensive environment variable access
export function getRequiredEnv(key: string): string {
  const value = process.env[key]
  if (!value) {
    throw new Error(`Missing required environment variable: ${key}`)
  }
  return value
}

Path and Platform Differences

IssueWindowsmacOSLinux
Path separator\//
Case sensitivityNoNo (default)Yes
Line endings\r\n\n\n
// Bug: works on Mac (case-insensitive), fails on Linux
import { UserAvatar } from '@/components/userAvatar'
// Actual file: components/UserAvatar.tsx — fix: match exact case

Version Conflict Diagnosis

> I'm getting "Cannot find module 'react-dom/client'" in CI
> but it works locally. Compare my local Node/npm versions with CI.
node -v          # Local: v22.x, CI: v18.x
npm -v           # Local: 10.x, CI: 9.x

Create a .nvmrc and add engines to package.json to prevent version drift:

{
  "engines": { "node": ">=20.0.0", "npm": ">=10.0.0" }
}

For more on configuring Claude Code's environment, see the Settings Guide.

7. Performance Debugging

Performance bugs are subtle — the app works, just slowly.

Frontend Performance

> Run a Lighthouse audit on the blog list page. Focus on LCP, CLS, and TBT.
> Suggest specific fixes for any metric below 90.
Core Web VitalGoodNeeds ImprovementPoorCommon Fix
LCP< 2.5s2.5-4.0s> 4.0sOptimize images, preload fonts
FID / INP< 100ms100-300ms> 300msCode split, defer non-critical JS
CLS< 0.10.1-0.25> 0.25Set image dimensions, avoid layout shifts

Bundle Analysis

> The initial page load is 2.3MB of JavaScript.
> Analyze the bundle and identify the largest packages,
> lazy-loadable packages, and duplicate packages.
# Enable Next.js bundle analyzer
ANALYZE=true npm run build
> The bundle analysis shows:
> - lodash: 72KB (only using _.debounce)
> - moment: 230KB (only using format)
> - highlight.js: 180KB (loading all languages)
>
> Replace each with a lighter alternative or tree-shakeable import.

Memory Leak Detection

> The app's memory grows from 150MB to 800MB over 2 hours.
> Check for event listeners not cleaned up, intervals not cleared,
> and growing caches without eviction.
// Bug: event listener leak in React component
function SearchBar() {
  const [query, setQuery] = useState('')
 
  // Bug: new listener on every render, never removed
  useEffect(() => {
    window.addEventListener('keydown', handleKeyDown)
  }) // Missing dependency array
 
  // Fix: add cleanup and dependency array
  useEffect(() => {
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [])
}
// Bug: growing cache with no eviction
const cache = new Map<string, unknown>()
export async function getCachedData(key: string) {
  if (cache.has(key)) return cache.get(key)
  const data = await fetchData(key)
  cache.set(key, data) // Cache grows forever — use LRU cache instead
  return data
}

Database Slow Query Debugging

> The /api/posts endpoint takes 3-5 seconds to respond.
> Add query timing to identify which database query is slow.
// lib/db-debug.ts
export async function timedQuery<T>(name: string, queryFn: () => Promise<T>): Promise<T> {
  const start = performance.now()
  try {
    const result = await queryFn()
    const ms = performance.now() - start
    if (ms > 1000) console.warn(`[SLOW QUERY] ${name}: ${ms.toFixed(0)}ms`)
    return result
  } catch (error) {
    console.error(`[FAILED QUERY] ${name}: ${(performance.now() - start).toFixed(0)}ms`, error)
    throw error
  }
}
 
// Usage
const posts = await timedQuery('getAllPosts', () =>
  db.select().from(postsTable).where(eq(postsTable.published, true))
)

For more on code optimization techniques, see the Refactoring Guide.

8. Production Debugging

Production bugs are the highest-stakes scenario — you can't add console.log and refresh.

Log Aggregation Analysis

> Here are the last 100 error logs from production (from CloudWatch):
> [paste logs]
> Group by error type, frequency, affected endpoints, and time pattern.
> Prioritize which to fix first based on user impact.

Claude Code identifies patterns:

- 45% "ECONNRESET" on /api/checkout → database connection pool exhausted
- 30% "TimeoutError" on /api/search → search service slow under load
- 15% "TypeError" on /blog/[slug] → specific post has malformed frontmatter
- 10% "CORS" on /api/upload → new CDN domain not in allowed origins

Priority: checkout (highest impact) → frontmatter (easy fix) → CORS → search

Error Monitoring Integration

// components/ErrorBoundary.tsx
'use client'
import * as Sentry from '@sentry/nextjs'
import { Component, type ReactNode } from 'react'
 
interface Props { children: ReactNode; fallback: ReactNode }
 
export class ErrorBoundary extends Component<Props, { hasError: boolean }> {
  state = { hasError: false }
  static getDerivedStateFromError() { return { hasError: true } }
 
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    Sentry.captureException(error, {
      contexts: { react: { componentStack: errorInfo.componentStack } },
    })
  }
 
  render() {
    return this.state.hasError ? this.props.fallback : this.props.children
  }
}

Reproducing Production Bugs Locally

> Blog post page crashes on mobile Safari: "ReferenceError: structuredClone is not defined"
> Device: iPhone 12, iOS 16.2. Help me reproduce this locally.
// structuredClone unavailable in WKWebView (in-app browsers)
// Fix: add a polyfill
if (typeof structuredClone === 'undefined') {
  globalThis.structuredClone = <T>(obj: T): T => JSON.parse(JSON.stringify(obj))
}

Canary Releases and Feature Flags

For risky fixes, use feature flags to roll out to a subset of users:

// lib/feature-flags.ts
type FeatureFlag = 'new-checkout' | 'v2-search'
 
const FLAGS: Record<FeatureFlag, { enabled: boolean; rollout: number }> = {
  'new-checkout': { enabled: true, rollout: 0.1 },  // 10%
  'v2-search': { enabled: false, rollout: 0 },
}
 
export function isFeatureEnabled(flag: FeatureFlag, userId: string): boolean {
  const config = FLAGS[flag]
  if (!config.enabled) return false
  const hash = [...(userId + flag)].reduce(
    (h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0
  )
  return (Math.abs(hash) % 100) / 100 < config.rollout
}

9. CLAUDE.md Debugging Conventions

Your CLAUDE.md is the perfect place to encode debugging conventions. Here's a section you can add:

# Debugging Conventions
 
## Error Handling Rules
- All async functions must have try/catch with specific error types
- Never catch and swallow errors silently — always log or rethrow
- API routes must return structured error responses
- Client components must have error boundaries
 
## Logging Standards
- Structured logging (JSON in production)
- Levels: error, warn, info, debug
- Always include: timestamp, request ID, user ID (anonymized)
- Never log: passwords, tokens, PII
 
## When Debugging
1. Read the full error + stack trace before making changes
2. Form a hypothesis, add targeted logging
3. Verify fix doesn't break tests, add regression test
4. Remove debug logging before committing
 
## Debug Commands
- `npm run build 2>&1 | head -50` — check build errors
- `npx tsc --noEmit` — type check without building
- `DEBUG=* npm run dev` — verbose debug output
 
## Common Issues
- Hydration mismatch: browser-only APIs in SSR → wrap in useEffect
- "Module not found": check import path case sensitivity
- Stale data: check revalidation settings in fetch()

For the full CLAUDE.md configuration guide, see the CLAUDE.md Guide.

10. Complete Walkthrough: Debugging Next.js SSR Hydration Mismatch

Let's walk through a real-world debugging scenario end to end.

The Scenario

After adding a "last updated" timestamp to blog posts, you see:

Warning: Text content did not match. Server: "March 12, 2026 10:30 AM"
Client: "March 12, 2026 3:30 PM"

Step 1: Interpret the Error

The 5-hour difference is a timezone issue. The server and client format the same Date object in different timezones.

Step 2: Locate the Bug

> Find where the "last updated" date is formatted in the blog post page.

Claude Code finds formatDate in lib/utils.ts:

// lib/utils.ts — the problematic function
export function formatDate(dateString: string, locale: Locale): string {
  const date = new Date(dateString)
  return date.toLocaleDateString(locale === 'zh' ? 'zh-CN' : 'en-US', {
    year: 'numeric', month: 'long', day: 'numeric',
    hour: 'numeric', minute: '2-digit', // includes time — timezone-sensitive!
  })
}

Step 3: Root Cause Analysis

toLocaleDateString() uses the runtime's local timezone. The server (EST/UTC-5) renders "10:30 AM" while the client (UTC) renders "3:30 PM" — same timestamp, different timezone output.

Step 4: Generate the Fix

// Option 1: Use UTC explicitly (recommended for blogs)
export function formatDate(dateString: string, locale: Locale): string {
  return new Date(dateString).toLocaleDateString(
    locale === 'zh' ? 'zh-CN' : 'en-US',
    { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }
  )
}
 
// Option 2: Client-only rendering with useEffect (for user-local times)
// Option 3: suppressHydrationWarning (escape hatch — use sparingly)

For a blog, Option 1 is the best choice — one-line fix, no client-side JS, no layout shift.

Step 5: Apply, Verify, and Test

// lib/utils.ts — fixed
export function formatDate(dateString: string, locale: Locale): string {
  return new Date(dateString).toLocaleDateString(
    locale === 'zh' ? 'zh-CN' : 'en-US',
    { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }
  )
}

Verify with different timezones: TZ=UTC npm run start, TZ=America/New_York npm run start — all should render the same date.

Step 6: Regression Test

> Write a test for formatDate that verifies it produces the same output
> regardless of the system timezone. This should prevent the hydration
> mismatch from coming back.
// lib/__tests__/utils.test.ts
import { describe, it, expect } from 'vitest'
import { formatDate } from '../utils'
 
describe('formatDate', () => {
  it('formats dates consistently regardless of timezone', () => {
    expect(formatDate('2026-03-12T15:30:00Z', 'en')).toBe('March 12, 2026')
    expect(formatDate('2026-03-12T15:30:00Z', 'zh')).toBe('2026年3月12日')
  })
 
  it('handles midnight UTC without date shift', () => {
    expect(formatDate('2026-03-12T00:00:00Z', 'en')).toBe('March 12, 2026')
  })
})
# Run the test
npx vitest run lib/__tests__/utils.test.ts

Full Workflow Recap

Error → Interpret → Locate → Root cause → Fix → Verify → Regression test

Common Hydration Mismatch Causes

CauseExampleFix
Timezone differencesnew Date().toLocaleString()Add timeZone: 'UTC'
Math.random()Random IDs or keysUse deterministic IDs
Date.now()"Posted 3 minutes ago"Client-only rendering
window / documentwindow.innerWidth in renderWrap in useEffect
localStorageTheme from localStorageInline script in <head>
Conditional rendering{isMobile && <MobileNav />}CSS media queries

Debugging Checklist for Hydration Errors

> I have a hydration mismatch. Help me debug it:
> 1. What exact text/HTML differs between server and client?
> 2. Which component renders this content?
> 3. Does it use Date/time, random values, window/document, or localStorage?
> 4. Was this component recently changed?

This systematic approach works for any bug. Let Claude Code handle the tedious parts while you provide context and make decisions.

For debugging failing tests, see Section 6 of the Testing Guide. For git bisect, see the Git Workflow Guide.

Summary

ChapterKey Takeaway
AI partnerClaude Code accelerates hypothesis → verify → narrow scope
Error interpretationFull stack traces + context = faster diagnosis
Root cause analysisBinary search (code or git) narrows scope systematically
Runtime debuggingStructured logging beats shotgun console.logs
Async issuesRace conditions and unhandled rejections are the usual suspects
Environment issuesVersion pins, case sensitivity, and env var validation prevent "works on my machine"
PerformanceMeasure first (Lighthouse, bundle analyzer), then optimize
ProductionLog aggregation, error monitoring, and feature flags for safe rollouts
CLAUDE.mdEncode debugging conventions so Claude Code follows them every time
WalkthroughSystematic debugging: interpret → locate → analyze → fix → verify → test

Debugging doesn't have to be a solo struggle. Describe the symptoms clearly, let Claude Code generate hypotheses, and use your judgment to guide the investigation.

Recommended Reading