Skip to content

Error-Driven Development

Error-driven development treats each compiler error, test failure, or runtime exception as a precise, machine-generated specification: it states what is wrong, where, and what the system expected instead. The loop is to make a change, run verification, hand the first error to the agent with targeted context, and fix its root cause.

CI is red. Fourteen TypeScript errors, three failing tests, and a deprecation warning. The stack trace points at payment.ts:212, you have already burned twenty minutes guessing which of the last six commits broke it, and the fix you just pushed turned one failing test into three. Staring harder at the error is not working.

The two instincts at this point are both wrong. Fixing everything by hand ignores what the agent is actually good at. Pasting the whole log in with “fix everything” gives it too much at once and produces cascading “fixes” that introduce new problems.

The error message is the most precise specification you have of what is wrong, and it is exactly the input an AI assistant is best at consuming — better than any prompt you could write from scratch. Error-driven development leans into that: instead of aiming for a perfect first draft, run a tight error → fix → re-run loop and let each failure steer the next change. Done deliberately, it converges fast even on gnarly cascades.

  • A repeatable error-fix-rerun loop you can run in any of the three tools
  • Copy-paste prompts for a production stack trace, a compiler cascade, and a failing-test loop
  • The per-tool mechanics: who runs the tests, who rolls back a bad fix, who iterates unattended
  • A way out when fixing one error keeps producing three more
  • An MCP shortcut that pulls the Sentry issue for you instead of copy-pasting stack traces
  • A habit for turning a hard-won fix into a rule in CLAUDE.md, .cursor/rules, or AGENTS.md

Rather than trying to prevent every error, lean on them as the fastest feedback mechanism available:

  1. Make a change (or let the agent make one)
  2. Run the verification step (build, test, lint, type-check)
  3. Read the first error
  4. Feed it to the agent with targeted context
  5. Let the agent fix the root cause
  6. Repeat from step 2

The critical discipline is one error at a time. Error messages cascade: a single root cause produces dozens of downstream errors, and fixing the first one usually makes half the others disappear. Fixing error fourteen while error one is still there is wasted work.

The cycle is identical everywhere — surface an error, give the agent enough context, apply the fix, re-run the exact thing that failed. What differs is who runs the command and how you back out a bad fix.

In agent mode, Cursor runs the tests or build itself, reads the terminal output, and iterates without you copy-pasting — after a failing command you can simply say “fix the error in the terminal.” The safety net is checkpoints: every agent edit is a restore point, so when a fix makes things worse you roll back to the last green state in one click instead of untangling it.

When you do paste an error, paste it with the context that makes it actionable:

I ran npm run type-check and got this error:
src/services/notification.ts:42:5 - error TS2345:
Argument of type 'string' is not assignable to parameter
of type 'NotificationPayload'.
Fix the root cause. The function signature in @src/services/notification.ts
expects a NotificationPayload object, not a raw string. Check the caller
at line 42 and the type definition in @src/types/notification.ts.

Best when you want to watch the loop happen and intervene the moment it goes sideways.

A user hit a crash and your error tracker captured the exception. The fastest path is to hand over the trace plus the files it implicates — the full exception and stack, not just the top line, because the top frame is where the value was read, rarely where it went wrong.

The fix should address where total became undefined — an upstream cart with no items, say — not bolt an ?. onto line 212. Re-run the failing path; if a new error surfaces, feed it back and repeat.

You changed the signature of a core function and the type-checker lit up with thirty errors across the codebase. This is the loop’s sweet spot, because the errors are an exact, machine-generated worklist. Do not fix anything by hand first; let the full list materialize, then delegate the whole thing.

The agent fixes call sites, re-runs the type-checker, and repeats. An hour of tedium by hand finishes in a few cycles, because the agent never gets bored on call site twenty-seven.

The strongest form of the loop is to write the failing test first and let the agent drive itself green against it. The test is an unambiguous oracle, so the loop terminates on its own.

That last sentence matters. Without it, an over-eager agent will sometimes “fix” the failure by loosening the assertion. Pin the test as the spec.

A compiler worklist shrinks as you work it. The dangerous kind of cascade does the opposite: the agent fixes error 1, which introduces error 2, which when fixed introduces error 3, and you burn the context window on whack-a-mole. The tell is the same file appearing in every round. When that happens, stop fixing errors and go after the structure.

When the same file keeps appearing, zoom out:

We've been fixing errors in notification.ts for three iterations
and new ones keep appearing. Stop fixing individual errors.
Instead:
1. Read the full file @src/services/notification.ts
2. Read the types it depends on @src/types/notification.ts
3. Read the test file @src/services/__tests__/notification.test.ts
4. Identify the structural problem causing the cascade
5. Propose a fix for the root architectural issue
Do not modify any files until I approve the approach.

The most valuable errors are the ones you never see again. When an error turns out to be non-obvious, capture the lesson in the project’s configuration so the agent avoids the same mistake in every future session.

Add a project rule in .cursor/rules/errors.md:

---
description: "Common errors and their fixes for this project"
alwaysApply: true
---
## Known Error Patterns
- When modifying notification types, always update both
`src/types/notification.ts` AND the Zod schema in
`src/validators/notification.ts`. They must stay in sync.
- Redis connection errors in tests: run `docker compose up -d redis`
before running the test suite.

Where the error-fix-rerun loop breaks down

Section titled “Where the error-fix-rerun loop breaks down”
  • Chasing the wrong error in a cascade. The first error often causes the rest. Tell the agent to fix the earliest root error and re-run before touching the others.
  • Fixing the symptom, not the cause. Wrapping an undefined access in ?. stops the crash without explaining why the value was missing. Ask “where did this become undefined?”, not “stop the throw.”
  • The agent suppresses instead of fixing. @ts-ignore, as any, empty catch blocks, and eslint-disable in the output all mean symptom management. Be explicit: do not suppress errors, fix the underlying issue.
  • Looping forever on a flaky test. A non-deterministic failure lets the agent “fix”, re-run, see green, and declare victory — then it flakes again in CI. Quarantine the flake first; do not run the loop against it.
  • Edit-the-test escape hatch. Agents sometimes make a failing assertion pass by weakening it. Add “do not modify the test” to any test-driven prompt.
  • Context blindness. Pasting only the top stack frame hides the real culprit deeper in the trace. Give the full trace and name the suspect files.
  • Too many errors to process. If the type-checker produces 200+ errors, do not feed them all in. Work the first three to five — they are usually the root causes — then re-run and see how many remain.
  • The error message is genuinely opaque. Segfaults and generic “internal server error” carry no specification. Switch from error-driven to hypothesis-driven: add logging, reproduce, narrow down by elimination.
  • The error is in generated code. When the broken file is one the agent wrote from scratch, deleting it and regenerating from a more constrained prompt usually beats patching it.

Where to go next with error-driven development

Section titled “Where to go next with error-driven development”