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
The debugging workflow, start to finish
Section titled “The debugging workflow, start to finish”-
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: -
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.tsShow me the exact line where it fails and the state of all variables at that point. -
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 47through the service layer and into the database query.Show me the data shape at each step. Where does the valuebecome undefined? -
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 theJWT token. Prove it: show me the specific line where theundefined value originates, and explain why it only happensfor 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 setCLAUDE_CODE_EFFORT_LEVEL=highbefore launching. -
Fix the bug and write a regression test
Fix the bug. Then write a test that reproduces the exactscenario that caused it -- expired session token with avalid user ID. The test should fail without the fix andpass with it. Then run the full test suite and show me anynew failures. -
Check for similar bugs elsewhere
Search the codebase for other places that use the samepattern that caused this bug. Are there other middlewarefunctions that assume the token payload is always present?List them so I can fix them proactively.
Piping errors straight into the terminal
Section titled “Piping errors straight into the terminal”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.
From your dev server
Section titled “From your dev server”# Pipe a failing test directly to Claudenpm 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."From production logs
Section titled “From production logs”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:
# Grab recent errors and analyze themgrep "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:
# Pipe filtered logs to Claude Codegrep "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?"From CI output
Section titled “From CI output”# Pipe CI failure output to Claudegh 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."Prompts for the bug types that recur
Section titled “Prompts for the bug types that recur”Stack traces from production
Section titled “Stack traces from production”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 trigger3. Read the source files involved and explain what went wrong4. Suggest a fix that addresses the root cause, not just the symptomType errors that point at the wrong file
Section titled “Type errors that point at the wrong file”This TypeScript error makes no sense to me:
[paste TypeScript error]
Read the file and its imports. Trace the type through every transformationto find where the type mismatch actually originates. It might not be in thefile the error points to.Race conditions and intermittent failures
Section titled “Race conditions and intermittent failures”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 operationsthat might resolve in a different order depending on timing. Identify:1. Any shared mutable state2. Any missing await calls3. Any operations that assume sequential execution4. Any cleanup that runs before async operations completeMemory leaks
Section titled “Memory leaks”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 atthe 4-hour mark.
Read our event handler code in src/handlers/ and look for:1. Event listeners that are added but never removed2. Arrays or maps that grow without bounds3. Closures that capture large objects4. Streams that are opened but never closed“It works locally but fails in CI”
Section titled ““It works locally but fails in CI””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.0CI uses: v20.10.0
Read the test file and look for:1. Environment-dependent code (paths, timezones, locale)2. Timing-sensitive assertions3. Missing test fixtures or setup steps4. Order-dependent tests that assume state from a previous testLatency that appeared with a deploy
Section titled “Latency that appeared with a deploy”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 queries2. Look for any new N+1 query patterns in the changed files3. Check if any new middleware was added to the request pipeline4. Look for blocking I/O operations that could explain the latency
Focus on database query changes first -- that is the most common cause.Finding the commit that broke it
Section titled “Finding the commit that broke it”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 servicesdirectory. Which commit introduced the change that could cause"TypeError: Cannot read property 'id' of null" in the orderprocessing 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 regression3. Check out that commit and run the relevant test4. 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:
Splitting a wide bug across sub-agents
Section titled “Splitting a wide bug across sub-agents”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.
Automated triage in headless mode
Section titled “Automated triage in headless mode”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.
# Automated error analysis in CIclaude -p "Analyze the test failures in this output andcategorize 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.jsonThat 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.
Where to go next after fixing the bug
Section titled “Where to go next after fixing the bug”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.