Skip to content

CLI Debugging Workflow

Debugging with Claude Code follows a fixed sequence: pipe the error in with full context, let Claude trace the execution path across every file in the stack trace, verify the root cause before any fix is applied, then fix and lock the behavior in with a regression test. Git bisection and log parsing handle regressions and production-only failures.

It is 2 AM and CI is red. The message is “Cannot read properties of undefined (reading ‘map’)”, the stack trace touches six files across two services, and your teammate says it worked yesterday. Git blame points at a merge commit with 40 changed files. You could spend the next two hours adding console.log statements.

Or you could pipe the error into Claude Code and have a root cause in five minutes. The developers who debug fastest do not just paste an error and ask for a fix — they follow a workflow: give Claude the error with full context, let it trace the execution path, verify the diagnosis before applying anything, and write a test that prevents the regression.

What a systematic debugging workflow gives you

Section titled “What a systematic debugging workflow gives you”
  • A repeatable path from error message to root cause in minutes, for any bug
  • Prompts that give Claude enough context to diagnose real bugs instead of guessing
  • The pipe-and-diagnose technique for dev-server, production, and CI output
  • Bug-type playbooks for stack traces, type errors, race conditions, memory leaks, and CI-only failures
  • Bisection strategies for finding the commit that introduced a regression
  • Headless-mode patterns for automated error triage in CI
  1. Give Claude the full error context

    The quality of the diagnosis depends entirely on the quality of the input. A bare error message gets you a guess. A stack trace with context gets you a root cause. The fastest way in is a pipe:

    Terminal window
    cat error.log | claude -p "Analyze this error. What is the root cause and which file should I look at first?"

    Then continue interactively in the same session with claude -c, which keeps the error context loaded. When you have more than a log to offer, spend the extra thirty seconds on a structured report:

  2. Reproduce the failure

    A bug you have watched fail is worth ten you have only read about:

    Run the failing test: npx jest src/payments/__tests__/process.test.ts
    Show me the exact line where it fails and the state of all variables at that point.
  3. Let Claude trace the execution path

    Claude reads the files in the stack trace, follows imports, checks types, and builds a picture of what went wrong. Do not rush this step — the trace is where the bug is found.

    Trace the request flow from src/routes/orders.ts line 47
    through the service layer and into the database query.
    Show me the data shape at each step. Where does the value
    become undefined?
  4. Verify the diagnosis before applying the fix

    Claude might identify the wrong root cause, especially for intermittent bugs. Before writing any fix, make it prove the claim:

    You're saying the bug is in the middleware that parses the
    JWT token. Prove it: show me the specific line where the
    undefined value originates, and explain why it only happens
    for users with expired sessions.

    Extended thinking is on by default in Claude Code, so no magic keyword is needed to trigger it. For the hardest race conditions, raise the reasoning depth first: pick a higher effort level in /model, or set CLAUDE_CODE_EFFORT_LEVEL=high before launching.

  5. Fix the bug and write a regression test

    Fix the bug. Then write a test that reproduces the exact
    scenario that caused it -- expired session token with a
    valid user ID. The test should fail without the fix and
    pass with it. Then run the full test suite and show me any
    new failures.
  6. Check for similar bugs elsewhere

    Search the codebase for other places that use the same
    pattern that caused this bug. Are there other middleware
    functions that assume the token payload is always present?
    List them so I can fix them proactively.

Claude Code’s terminal-native design means error output goes in directly. This is the shortest path from failure to diagnosis, and it works wherever the error already prints.

Terminal window
# Pipe a failing test directly to Claude
npm test -- --run tests/services/order.test.ts 2>&1 | \
claude -p "This test is failing. Read the test file and the \
source code it tests. Diagnose the root cause and fix it."

Filter before you pipe. A hundred relevant lines beat a hundred thousand raw ones, and the filtering is what lets Claude group by cause instead of paraphrasing your log:

Terminal window
# Grab recent errors and analyze them
grep "ERROR" /var/log/app/production.log | tail -50 | \
claude -p "Analyze these production errors. Group them by \
root cause. For each group, identify the source file and \
suggest a fix. Prioritize by frequency."

When you are already chasing a specific incident, narrow the question to it:

Terminal window
# Pipe filtered logs to Claude Code
grep "ERROR\|WARN" /var/log/app.log | tail -100 | \
claude -p "Categorize these errors. Which are most frequent? Which are likely related to the payment processing bug we are investigating?"
Terminal window
# Pipe CI failure output to Claude
gh run view 12345 --log-failed | \
claude -p "This CI run failed. Identify which test failed, \
read the relevant source code, and explain what broke. \
Check recent commits to see if a specific change caused it."

The generic version of this prompt gets a generic answer. The one that works forbids generic fixes explicitly and ends with a passing test:

When you want the call chain itself rather than a fix, ask for the chain:

Here is a stack trace from production:
[paste stack trace]
1. Identify the root cause (not just the symptom)
2. Trace the call chain from the error back to the original trigger
3. Read the source files involved and explain what went wrong
4. Suggest a fix that addresses the root cause, not just the symptom
This TypeScript error makes no sense to me:
[paste TypeScript error]
Read the file and its imports. Trace the type through every transformation
to find where the type mismatch actually originates. It might not be in the
file the error points to.

Race conditions are hard to debug because they are timing-dependent, and a vague prompt gets a vague theory. Name the failure precisely, then hand Claude the checklist of things that actually cause them:

We have an intermittent test failure in tests/services/payment.test.ts.
It passes 9 out of 10 times. The error is "expected 'processing'
but received 'completed'".
Read the test and the payment service. Look for any async operations
that might resolve in a different order depending on timing. Identify:
1. Any shared mutable state
2. Any missing await calls
3. Any operations that assume sequential execution
4. Any cleanup that runs before async operations complete
Our Node.js service memory grows from 200MB to 1.2GB over 6 hours,
then crashes with OOM. I took heap snapshots at startup and at
the 4-hour mark.
Read our event handler code in src/handlers/ and look for:
1. Event listeners that are added but never removed
2. Arrays or maps that grow without bounds
3. Closures that capture large objects
4. Streams that are opened but never closed
This test passes on my machine but fails in CI. Here's the CI output:
[paste output]
Here's my local Node version: v20.11.0
CI uses: v20.10.0
Read the test file and look for:
1. Environment-dependent code (paths, timezones, locale)
2. Timing-sensitive assertions
3. Missing test fixtures or setup steps
4. Order-dependent tests that assume state from a previous test
Our API response times increased from 50ms to 800ms after the last deploy.
Run these diagnostics:
1. Check git diff HEAD~1 for changes to database queries
2. Look for any new N+1 query patterns in the changed files
3. Check if any new middleware was added to the request pipeline
4. Look for blocking I/O operations that could explain the latency
Focus on database query changes first -- that is the most common cause.

Claude Code is git-aware, which turns “it worked last Tuesday” from a shrug into a search space. Start wide:

This bug started appearing after last Tuesday's deploy. Run:
git log --oneline --after="2026-02-03" -- src/services/
Then read the diffs for each commit that touched the services
directory. Which commit introduced the change that could cause
"TypeError: Cannot read property 'id' of null" in the order
processing flow?

When you have a known-good commit and a reproducing test, narrow it by binary search instead:

The /api/search endpoint was working correctly in commit abc123 (2 weeks ago)
but is broken in HEAD. Help me bisect:
1. Run: git log --oneline abc123..HEAD -- src/api/search/
2. Identify the most likely commit to have introduced the regression
3. Check out that commit and run the relevant test
4. If the test passes, the bug is in a later commit. If it fails, it is in this commit or earlier.
5. Narrow down to the exact commit using binary search.

Better still, let git do the search and Claude do the reading:

When a bug spans multiple parts of the system, use sub-agents to investigate in parallel without filling your main context with irrelevant code.

Use sub-agents to investigate this bug from multiple angles:
1. Trace the request from the API gateway through the auth
middleware to the order service. Find where the user object
loses its organization_id field.
2. Check the database migration history for the organizations
table. Was a column recently renamed or made nullable?
3. Search for all places in the codebase that read
user.organization_id and check if any of them handle
the undefined case.
Report findings so we can pinpoint the root cause.

Each sub-agent runs in its own context, reads as many files as needed, and reports back a focused summary. Your main session stays clean for the actual fix.

For teams that want error triage without a human in the loop, headless mode turns Claude Code into a debugging pipeline that emits structured output.

Terminal window
# Automated error analysis in CI
claude -p "Analyze the test failures in this output and
categorize them:
1. Flaky tests (timing-dependent, order-dependent)
2. Real bugs (code logic errors)
3. Environment issues (missing config, wrong versions)
For real bugs, identify the root cause file and line number.
For flaky tests, suggest how to make them deterministic.
$(cat test-output.log)" \
--output-format json > debug-report.json

That JSON report is what your CI posts as a PR comment or sends to Slack. When you want the pipeline to attempt the fix rather than only classify, hand it the test output inline:

Where debugging with Claude Code breaks down

Section titled “Where debugging with Claude Code breaks down”

Claude fixes the symptom but not the root cause. This happens when you paste just the error message without context. Always include the stack trace, when it happens, what changed recently, and how often it occurs. Instead of “fix the null pointer error,” say “the user object is null because the async fetch races with the render — fix the race condition, not the null check.”

The fix breaks something else. Claude fixed the bug but did not check for side effects. After every fix, run the full test suite, not just the test for the bug. Put it in the prompt: “After fixing the bug, run the full test suite and show me any new failures.”

Claude cannot reproduce the bug. Write a failing test first: “Before debugging, write a test that reproduces this exact scenario. Run it to confirm it fails.” A failing test is the most unambiguous bug report there is. For bugs that only manifest in production, supply production logs, environment details, and the specific data that triggers the issue — --append-system-prompt is a good place to put that standing context.

The bug is subtle and the answers are shallow. For bugs spanning many files or involving timing, keep the effort level at its default high setting and lower it only when you want faster, shallower responses. Adjust it in the /model picker or via CLAUDE_CODE_EFFORT_LEVEL. At high effort, Claude reasons through the problem more carefully before suggesting anything.

Bisection lies on a flaky test. If the test is intermittent, git bisect run will mark commits wrongly and confidently. Run the test several times at each point: git bisect run bash -c 'for i in 1 2 3; do npx jest test.ts || exit 1; done'.

Context fills up during a long session. Debugging accumulates file reads and test output fast. Run /compact Keep all error traces, test output, and diagnostic results to keep what matters and free the rest. If the diagnosis is already clear, the cheaper move is a fresh session carrying only the diagnosis, letting Claude implement the fix from scratch.

The bug is fixed and a regression test is in place. Next comes strengthening the rest of the suite so the following one is caught before it ships.