Claude Code Debugging Workflow: Observation, Reproduction, Fix, Test
Claude Code debugging workflow with TypeScript, Node, Vitest, prompts, pitfalls, tests, and CTA checks.
Claude Code can produce a plausible fix from an error message, but production debugging needs more than a plausible patch. A reliable debugging session separates observation, hypothesis, minimal reproduction, implementation, and regression test. When those steps are skipped, the bug may disappear from one screen while returning through another input, language version, or deployment path.
This article shows a concrete workflow for TypeScript, Node, and Vitest. Use it with Claude Code and TDD when you want the failing condition preserved, error handling patterns when the exception model itself is unclear, and build error triage loop when the proof command fails before you reach the real bug.
Give Claude Code an investigation order
A vague request such as “fix this error” encourages Claude Code to jump to the first reasonable patch. Instead, provide the full error, stack trace, reproduction steps, input shape, expected behavior, actual behavior, and recently changed files. Ask for three hypotheses before implementation. This forces the agent to separate facts from guesses.
The order matters. Observation captures what is known. A hypothesis explains what might be wrong. A minimal reproduction turns the hypothesis into a failing test or script. The fix should be narrow. The regression test should stay in the repository so the same bug cannot quietly return.
{
"name": "claude-debug-lab",
"private": true,
"type": "module",
"scripts": {
"test": "vitest run",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.6.0",
"vitest": "^3.0.0"
}
}
Example 1: Turn an undefined map error into a rule
The first bug is a common one: code calls map on data that has not arrived yet. The goal is not only to silence the crash. The domain rule is that a missing payload becomes an empty list, blank names are removed, and valid names are trimmed.
export type User = {
id: string;
name?: string | null;
};
export function normalizeUsers(users: User[] | null | undefined): string[] {
if (!Array.isArray(users)) return [];
return users
.filter((user): user is User & { name: string } => typeof user.name === "string")
.map((user) => user.name.trim())
.filter((name) => name.length > 0);
}
import { describe, expect, it } from "vitest";
import { normalizeUsers } from "../src/normalize-users.js";
describe("normalizeUsers", () => {
it("returns an empty list when the API payload is missing", () => {
expect(normalizeUsers(undefined)).toEqual([]);
expect(normalizeUsers(null)).toEqual([]);
});
it("keeps only usable display names", () => {
expect(
normalizeUsers([
{ id: "u1", name: " Masa " },
{ id: "u2", name: "" },
{ id: "u3", name: null },
]),
).toEqual(["Masa"]);
});
});
Use this prompt:
We see Cannot read properties of undefined (reading 'map').
normalizeUsers must tolerate missing API payloads and remove blank display names.
Add a failing Vitest case first, then make the smallest fix.
Run npm test and npm run typecheck at the end.
The trap is stopping at users ?? []. That may prevent the crash while leaving null names, blank names, and non-array payloads undefined as product behavior. Give Claude Code the rule you want, not only the exception you saw.
Example 2: Debug async retry without hiding the final error
Async retry bugs often appear intermittent. A good test checks call count, wait behavior, eventual success, and total failure. If the final error is swallowed, incident response becomes much harder because the symptom no longer points to the service that failed.
type RetryOptions = {
times: number;
delayMs: number;
sleep?: (ms: number) => Promise<void>;
};
const defaultSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
export async function retry<T>(
task: () => Promise<T>,
{ times, delayMs, sleep = defaultSleep }: RetryOptions,
): Promise<T> {
let lastError: unknown;
for (let attempt = 1; attempt <= times; attempt += 1) {
try {
return await task();
} catch (error) {
lastError = error;
if (attempt < times) await sleep(delayMs);
}
}
throw lastError instanceof Error ? lastError : new Error("Retry failed");
}
When asking Claude Code to test this, mention mock isolation, no broad refactor, and preserving the last error. Temporary logs are fine during investigation, but they should be removed before the final patch unless the product intentionally needs structured logging.
Example 3: Reproduce date boundaries before changing UI code
The third example is a date-boundary bug. It often appears as “March export is missing March 31 orders”, but the root cause is usually not the export button. It is usually the comparison between the selected month start and the next month start.
export type Order = {
id: string;
createdAt: string;
total: number;
};
export function exportMonthlyOrderIds(orders: Order[], month: string): string[] {
const [yearText, monthText] = month.split("-");
const year = Number(yearText);
const monthIndex = Number(monthText) - 1;
const start = new Date(Date.UTC(year, monthIndex, 1));
const end = new Date(Date.UTC(year, monthIndex + 1, 1));
return orders
.filter((order) => {
const createdAt = new Date(order.createdAt);
return createdAt >= start && createdAt < end;
})
.map((order) => order.id);
}
import { describe, expect, it } from "vitest";
import { exportMonthlyOrderIds } from "../src/export-orders.js";
describe("exportMonthlyOrderIds", () => {
it("includes orders from the first day through the last moment of the month in UTC", () => {
const orders = [
{ id: "feb-end", createdAt: "2026-02-28T23:59:59.999Z", total: 1000 },
{ id: "mar-start", createdAt: "2026-03-01T00:00:00.000Z", total: 2000 },
{ id: "mar-end", createdAt: "2026-03-31T23:59:59.999Z", total: 3000 },
{ id: "apr-start", createdAt: "2026-04-01T00:00:00.000Z", total: 4000 },
];
expect(exportMonthlyOrderIds(orders, "2026-03")).toEqual(["mar-start", "mar-end"]);
});
});
Tell Claude Code not to rely on local timezone parsing. The rule is simple: include the start of the selected month and exclude the start of the next month. That wording gives the agent a stable target and gives reviewers a test they can trust.
Copy-paste debugging prompt
Goal:
Find the cause, add a regression test, and fix the bug with the smallest useful diff.
Observations:
- Full error:
- Reproduction steps:
- Expected behavior:
- Actual behavior:
- Recently changed files:
Constraints:
- List three hypotheses before editing.
- Avoid broad refactors.
- Do not hide type errors with any.
- Remove temporary logs before finishing.
- Run npm test and npm run typecheck at the end.
Report:
- Root cause
- Files changed
- Regression test added
- Remaining risks
This turns Claude Code from a patch generator into an investigation partner. Reviewers get the root cause, evidence, and remaining risk before reading every line. For team workflows, copy the same fields into the review gate so AI-generated and human-generated diffs are held to the same standard.
Debugging without breaking the revenue path
Public-site debugging should also check CTAs. A content fix can push the free PDF form below the fold, change a Gumroad link, or make the consultation path harder to find. The more popular the article, the more important it is to treat revenue routing as part of the debugging surface.
Use a small routing rule when the bug fix touches article layout, product links, or forms.
const debuggingRoutes = {
first_error: "free_pdf",
repeated_bugfixes: "prompt_templates",
setup_or_ci_blocked: "setup_guide",
team_process_blocked: "consultation",
};
export function chooseDebuggingCta(intent) {
return debuggingRoutes[intent] ?? "free_pdf";
}
console.log(chooseDebuggingCta("repeated_bugfixes"));
The free cheatsheet fits beginners and comparison readers. 50 Prompt Templates fits repeated debugging and review work. The Setup Guide fits permissions, CI/CD, and setup blockers. Consultation fits team rollout and operating-model decisions.
What I verified for this article
This rewrite keeps clean UTF-8 text, executable code blocks, internal links, external links, and the free PDF, Gumroad, and consultation path. The next metrics to watch are PDF signups, Prompt Templates clicks, Setup Guide clicks, and Training page visits from this debugging slug.
Post-publication debugging check
When publishing a debugging article, do not finish at code correctness alone. Open the public URL and inspect h1, canonical, opening body, hero image, free PDF form, Gumroad links, and consultation links. On a popular article, a debugging workflow improvement can directly affect the reader’s next action. If the explanation is better but the PDF signup, Gumroad product, or consultation route is wrong, the revenue path is unfinished.
For localized versions, shared code examples are fine, but the explanation and CTA must be in the target language. The reader should understand the order of observation, reproduction, fix, and test without switching languages. During screenshot review, inspect the opening body, prompt-template area, and CTA area.
The next metrics are free PDF registrations, Prompt Templates clicks, Setup Guide clicks, and Training page visits from this slug. Debugging readers have active pain, so downstream movement matters more than raw pageviews. Pass those numbers to Claude Code before the next rewrite.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
Claude Code Quick Reference Cheatsheet
A free one-page reference for daily Claude Code work.
Keep the essential commands, file-reference patterns, CLAUDE.md reminders, prompting habits, review cues, and debugging workflow notes next to your editor.