CLAUDE LABJP
MCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attributionMCP — Support for the 2026-07-28 spec is rolling out across Claude. The protocol moves from bidirectional and stateful to request/response, so MCP servers can now live on serverless and edge infrastructureEXTENSIONS — Three official extensions have landed: MCP Apps for server-rendered UI, Tasks for async and long-running work, and Enterprise Managed Auth for IdP-based org-wide provisioningADOPTION — MCP passed 400 million monthly SDK downloads, roughly 4x growth this year, settling into its role as the standard way to connect agents to applicationsQUOTA — Today, August 19, is the last day of the 50 percent weekly usage boost for Claude Code subscribers. If you have long agent runs queued, this is the windowPRICING — Claude Sonnet 5's introductory rate of $2 per million input tokens and $10 output ends August 31; standard pricing of $3 and $15 takes over on September 1, twelve days outFIX — A bug where MCP v2 connections endlessly reopened subscriptions against servers with fixed timeouts is resolved, and a forward_user_identity setting was added for user attribution
Articles/Claude Code
Claude Code/2026-04-18Intermediate

Three Months of Cutting Technical Debt in Half with Claude Code — A Practical Record

A three-month hands-on record of tackling accumulated technical debt using Claude Code. Covers visualization, prioritization, and automated refactoring — with real numbers showing what actually changed.

technical debtrefactoring4Claude Code225code qualityindie dev10

When you've been running a personal app for a few years, there comes a day when you open a file and realize: I can't read my own code anymore. Comment-outs layered on top of each other, functions called from somewhere unknown, logic duplicated in five places with subtle variations. If that sounds familiar, this post is for you.

That was exactly my situation. Running two apps simultaneously while continuously adding features had left my codebase in what I call the "works but untouchable" state — functional, but too fragile to refactor safely.

So at the start of 2026, I committed three months to seriously tackling technical debt using Claude Code. This is the record of what I did, in what order, what worked, and where Claude hit its limits.

Step One: Build a Debt Map — Put Numbers on the Problem

The most common reason refactoring stalls before it starts is the question "where do I even begin?" Without a concrete map, you work on whatever annoys you most rather than what matters most.

The first thing I did was ask Claude Code to generate a technical debt inventory. Running this from the project root:

claude "Analyze this entire repository and produce a Markdown report covering:
1. All TODO/HACK/FIXME/XXX comments with file locations
2. Duplicate code patterns (50+ lines of similar logic)
3. High-complexity functions (5+ levels of nesting or conditionals)
4. Circular module dependencies
5. Files with zero test coverage (if test config exists)
 
For each item, add a 1-5 star rating on two axes: 'blast radius if it breaks' and 'difficulty to fix'."

Claude spent a few minutes scanning the whole repo and produced a surprisingly specific report. My numbers:

  • TODO/HACK comments: 47 instances (23 of them over two years old)
  • Obvious duplicate code: 8 patterns (~1,200 lines)
  • Circular dependencies: 3 module pairs
  • Files with zero test coverage: 67% of the codebase

Something shifts when you move from "my code is kind of a mess" to "I have 3 circular dependencies and 8 duplicate patterns." The problem becomes bounded, approachable. That map made it possible to prioritize without getting overwhelmed.

Prioritization: The Urgency × Impact Matrix

With a map in hand, the next challenge is deciding what to tackle first. My mistake in previous attempts was starting with whatever looked the ugliest. Aesthetically offensive code isn't always the most dangerous code.

The key to getting useful prioritization from Claude is including your real-world constraints:

claude "Using the debt report we just created, help me prioritize the work.
Constraints:
- I have 1-2 days per week available for refactoring
- I cannot break live functionality (this is a production app)
- I'm planning to add Feature X in three months, and its surrounding modules need to be clean first
 
Given these constraints, please categorize items into: 'this week', 'this month', and 'within three months'."

The output gave me something concrete: tackle the 8 duplicate code patterns first (this week and next week), skip the circular dependency resolution until month two because the blast radius is too large to handle quickly.

Crucially, telling Claude about the upcoming Feature X caused it to flag which debt items lived in that feature's neighborhood — and those jumped to the front of the queue.

Removing Duplicate Code: Safe Refactoring with Subagents

The highest-priority items were duplicate error handling patterns scattered across 8 API files. Each file had its own slightly different version of the same try/catch logic.

This is where Claude Code's subagent capability with --headless and git worktrees becomes genuinely powerful. You can experiment without touching your main branch at all:

# Create an isolated working environment
git worktree add /tmp/refactor-api-errors -b refactor/api-error-handling
 
claude --headless "
Working directory: /tmp/refactor-api-errors
Task: Consolidate the duplicated error handling logic found in these 8 files:
src/api/users.ts, src/api/products.ts, src/api/orders.ts, ... (etc.)
 
Steps:
1. Analyze the error handling patterns across all 8 files
2. Identify what can be shared vs. what must stay per-file
3. Create src/utils/apiErrorHandler.ts as a shared utility
4. Refactor all 8 files to use the new module
5. Verify no TypeScript errors remain
6. Output a final diff of all changes
"

The result: 8 patterns collapsed into one shared utility plus thin per-file wrappers. ~340 lines became ~95 lines.

Important caveat: I never merged the output directly. I always reviewed the diff, checked that type definitions were accurate and error messages hadn't changed, then decided whether to merge. The worktree approach means your main branch is never at risk while you're exploring.

Resolving Circular Dependencies: Understand Before You Fix

Circular dependencies took nearly a month — the longest stretch. That's because they're not really a code problem; they're a design problem. Module A importing module B which imports module A means the original separation of concerns was unclear.

Asking Claude to "just fix it" here would have been a mistake. Resolving circular dependencies requires redesigning module responsibilities — a judgment call that belongs to the developer, not the AI.

Instead, I asked Claude to analyze why the dependency existed:

claude "There's a circular dependency between src/store/userStore.ts and src/components/UserProfile.tsx.
Analyze why this pattern emerged, explain the design flaw, and suggest three approaches to resolve it.
Include trade-offs for each approach."

The three options it produced (event bus, context separation, dependency injection) gave me the vocabulary to make an informed decision. I chose, then asked Claude to implement. That "analyze → decide → implement" flow is the right one for structural problems.

Adding Tests to Untested Code: The Incremental Approach

With 67% of files having zero test coverage, trying to test everything at once is a trap. I used a narrowly scoped approach:

claude "Write tests for src/utils/priceCalculator.ts.
Don't try to cover everything — instead, identify the 3 functions in this file where a bug would most impact users, and write focused tests for those 3 functions using vitest."

"The 3 most impactful functions" rather than "100% coverage" was the prompt that made this sustainable. Over three months, coverage went from 0% to 38%. Not perfect — but the core user-facing paths are now protected.

Automating the Weekly Health Check: The Hook I Actually Run

The single highest-leverage part of these three months wasn't any individual refactor. It was measuring the same four numbers every week with the same ruler. Willpower alone didn't sustain that, so I wired it into a Claude Code hook.

One addition to .claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          { "type": "command", "command": ".claude/scripts/debt-healthcheck.sh" }
        ]
      }
    ]
  }
}

The script bails out immediately unless seven days have passed since the last run — otherwise it would fire on every session start and become noise you learn to ignore.

#!/usr/bin/env bash
set -euo pipefail
 
STAMP=".claude/.debt-last-run"
NOW=$(date +%s)
 
# Skip if the last run was under 7 days ago (604800 seconds)
if [ -f "$STAMP" ] && [ $(( NOW - $(cat "$STAMP") )) -lt 604800 ]; then
  exit 0
fi
 
OUT=".claude/debt/$(date +%Y-%m-%d).md"
mkdir -p "$(dirname "$OUT")"
 
claude -p "Inventory this repository's technical debt and report ONLY four metrics:
TODO/HACK comment count, duplicate pattern count, circular dependency pairs, and maximum
function complexity. One metric per line, format 'metric: value'. No prose, no preamble." > "$OUT"
 
# Show only the delta against last week's snapshot
PREV=$(ls .claude/debt/*.md 2>/dev/null | tail -2 | head -1)
if [ -n "$PREV" ] && [ "$PREV" != "$OUT" ]; then
  diff "$PREV" "$OUT" || true
fi
 
echo "$NOW" > "$STAMP"

Two details in there are deliberate.

The prompt constrains the output to four lines with no prose. Whatever a SessionStart hook prints to stdout lands directly in the session context. A few thousand words of report would burn context before you've even stated what you came to do. Four numbers are enough to decide whether anything moved.

The reports are also date-stamped rather than overwritten. Keeping the history lets diff surface the change instead of the level — and change is what actually prompts action. Nobody reacts to "19 TODOs." People react to "three more than last week."

One caution: a hook script that exits with code 2 is treated as blocking. A health check exists to inform, not to gate your work, so the diff return value is swallowed with || true and the script always exits clean. Get this wrong and the tool you built to measure debt becomes the thing standing between you and your editor.

Three Things That Didn't Work

A record of only the successful steps isn't reproducible. And as an indie developer with no reviewer to catch me, the time it took to notice each of these was the real cost. Here's what failed outright.

I handed over too many files at once. For the duplicate-code consolidation, my first attempt passed all eight files in a single instruction. The first four came back cleanly refactored against a shared utility — and the last four had quietly grown a second helper with slightly different conventions. It resembles the way a decision made an hour ago fades during a long stretch of work. Splitting into batches of roughly five, and explicitly naming the path of the shared module created in the previous batch, ended the drift.

The generated tests mirrored the implementation. This was my first stumble on the testing work. Feed Claude the implementation and ask for tests, and you get assertions transcribed from the code's own logic — which pass whether or not the code is correct. The fix was to separate the inputs: hand over a short spec note instead of the implementation file, with the instruction "derive expected values from this spec; do not read the implementation." It costs more setup time. Tests written that way caught two real bugs.

I turned a metric into a goal. Maximum function complexity of 18 was the number that bothered me most, so for a stretch I asked directly for lower complexity. Conditionals got mechanically extracted into small functions, and following any single code path started requiring three hops through the call stack. The metric improved while readability got worse. These numbers are for noticing regression — optimizing them directly is a different activity, and not a useful one.

Three Months Later: The Numbers

At the end of month three, I ran the same analysis that generated the original debt map. Results:

MetricStartMonth 3Change
TODO/HACK comments4719−60%
Duplicate code patterns82−75%
Circular dependencies30−100%
Test coverage0%38%+38pt
Maximum function complexity187−61%

The numbers look good. But the most meaningful change wasn't quantitative — it was that adding new features stopped feeling scary. The "works but untouchable" state became "imperfect but workable."

What I Learned About Claude Code and Technical Debt

A few things that shaped my approach, worth keeping in mind:

Don't ask Claude to "just fix everything." Debt reduction requires design decisions. Claude is excellent at generating options, implementing a chosen approach, and writing tests — but which approach to take is yours to decide.

The worktree pattern is your safety net. Running subagents in isolated worktrees means zero risk to your production code while you experiment. This alone makes Claude Code worth using for refactoring work.

Run the health check weekly. Re-running the same debt analysis once a week takes minutes and tells you immediately if you're sliding backward. I've since automated this via a Claude Code hook.

Done is better than perfect. Technical debt never reaches zero. The goal is moving from "works but untouchable" to "touchable and improving." Once you reach that state, the velocity gains compound on their own.

The next step is automating ongoing quality monitoring — a hook that runs on every PR, flags any metrics that are worsening, and leaves a comment. Building that system is the logical continuation of this three-month effort.

If you're standing in front of a codebase that works but feels untouchable, I hope some of this gives you a place to start.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-07-07
I Could Detect Failures — Then the Alerts Grew So Loud I Stopped Noticing
The moment I could detect silent job failures, my phone started buzzing too often to be useful. Here is how I collapsed alerts by action rather than event, added hysteresis and quiet hours, and built a three-step escalation so only the pages worth waking me survive.
Claude Code2026-06-30
Fixing Blurry Wallpapers on New iPhones with Claude Code: Safely Growing a Per-Device Resolution Map
Why wallpapers go blurry or letterboxed on brand-new iPhones, and how to collapse scattered device branches into a single source of truth you can extend in one line — walked through as a real Claude Code refactor with before/after code.
Claude Code2026-06-20
Handing Claude Design Mockups Straight to Claude Code: Folding the Design-to-Code Loop
The June 17 Claude Design update added codebase-aware generation. Here's how to make your tokens the single source of truth, hand mockups to Claude Code, and collapse the design-to-code round trip — with code you can copy.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →