Claude writes code, a test breaks, and it hands the error back to you to deal with.
A self-fixing agent doesn't: it reads its own failure, patches the cause, and runs again until green.
Set it up once and you stop being your agent's unpaid debugger.
Here's the full setup you need đ
Before we dive in, I share daily notes on AI & vibe coding in my Telegram channel: https://t.me/zodchixquantđ§

Why one pass isn't enough
By default Claude runs a task once. A test fails, it reports the error and waits. You read it, paste it back, tell it to try again. You're doing the debugging, it's just typing.
A self-fixing agent flips that. It reads its own failure, works out what kind of bug it is, fixes that cause, and runs again, the way a debugger thinks. You step in only when it passes or gets genuinely stuck.
File 1: the fix command
This is the loop you actually run. Drop into .claude/commands/fix.md:
1---2description: Run a task and self-fix until all tests pass3argument-hint: <task>4allowed-tools: Read, Write, Edit, Glob, Grep, Bash5model: sonnet6---78Task: $ARGUMENTS910Loop:111. Implement the task.122. Run the test suite: `npm test` (or pytest, cargo test).133. If all tests pass: stop and show me the result.144. If a test fails: classify the error type, name the root cause,15 fix that cause, then go back to step 2.165. Cap at 5 attempts. Announce the attempt number each round.1718The classify-then-fix order is the whole point. Diagnosis before19edit, every round. Never weaken a test to pass, fix the code.
You type /fix add pagination to the users endpoint and the agent writes, tests, and repairs itself until the suite is green.
File 2: the auto-check hook
The command works on demand. The hook makes self-checking automatic on every edit.
Add to .claude/settings.json:
1{2 "hooks": {3 "PostToolUse": [4 {5 "matcher": "Write|Edit",6 "hooks": [7 { "type": "command", "command": "npm test --silent 2>&1 | tail -5 || echo 'TESTS FAILING, fix before continuing'" }8 ]9 }10 ]11 }12}
Now every time Claude edits a file, the tests run and the result lands back in its view.
A failure shows up immediately, so the agent fixes it in the same flow instead of moving on and stacking more changes on top of broken code.
File 3: teach it to read the error
This is the file that separates a real debugger from a guesser. Most agents see "test failed" and start randomly editing.
A good one reads the error type first, because each type points to a different cause. Put this in CLAUDE.md:
1## How to read a failure before fixing it23Always name the error type first. Each one means something different:45- Assertion failure (expected X, got Y): the logic is wrong.6 Trace back from the assertion to where the value was built.7 Fix the calculation, not the test's expectation.89- Type error / undefined is not a function: a shape mismatch.10 Something is null, or the wrong type reached this line.11 Find where that value originates, guard or correct it there.1213- Timeout / hang: an await is missing, or a promise never resolves.14 Look for async calls without await, or a condition that never flips.1516- Import / module not found: a path or dependency problem, not logic.17 Fix the path or install, do not touch the code that uses it.1819- Flaky (passes sometimes): shared state or timing, never random.20 Look for order-dependent tests or unmocked time/network.2122Rule: state the type and the one-sentence root cause out loud23before editing. If you can't name the cause, you're guessing. Stop.
A type error and an assertion failure need opposite moves: one is a data-shape problem upstream, the other is wrong logic at the spot. Classify first and the agent fixes the right thing.
Skip it and it edits at random until red disappears, which is how you get a green suite that's quietly broken.
What you actually see
You type one line:
1/fix the date formatter returns wrong timezones
Then it works on its own:
1Attempt 12 ran tests â FAILED3 type: assertion (expected 5, got 11)4 cause: page size not applied to the query5 fixed the LIMIT clause67Attempt 28 ran tests â FAILED9 type: type error (offset is undefined)10 cause: page 1 sends no offset, defaulted wrong11 added offset = (page-1)*size1213Attempt 314 ran tests â ALL GREEN (7/7)1516Fixed in 3 attempts. Review the diff?
You never pasted an error back. It read its own failures, named each cause, and fixed until the suite passed.
Common mistakes
Fixing before diagnosing. The biggest one. An agent that edits before naming the error type is guessing, and guessing turns red green by luck, not by fixing. The classify-first rule is the whole defense.
Treating every error the same. A timeout and an assertion failure have nothing in common, but an agent without the error-type guide treats both as "something's wrong" and edits the nearest line. Half your fixes land in the wrong file.
Patching the symptom. Wrapping a throwing call in try/catch makes the test pass and leaves the bug. The protocol forbids silencing errors for this exact reason.
Letting it weaken tests. An agent allowed to edit tests will eventually delete the failing one to reach green. A passing suite that proves nothing is the worst outcome, so this rule is non-negotiable.
The 5-minute setup
2 minutes: create .claude/commands/fix.md.
1 minute: add the PostToolUse hook to settings.json.
1 minute: add the self-debug protocol to CLAUDE.md.
1 minute: run /fix on a known bug and watch it work the cause, attempt by attempt.
You stop being the human who relays errors. The agent reads its own red and fixes until it's green. It didn't get smarter, it just stopped handing the problem back to you.
Thanks for reading!
I share daily notes on AI, finance, and vibe coding in my Telegram channel: https://t.me/zodchixquant






