Claude Code is running pnpm test for the eighth time. Each iteration: edit the same expect(...).toBe(...) line, run again, fail again, revert. Or it’s ping-ponging between two interpretations of “fix the auth bug” — fixing OAuth, breaking magic-link, fixing magic-link, breaking OAuth. Tokens burn, progress bar doesn’t move. The agent is in a loop.
95% of loops aren’t “the model can’t fix this” — they’re a broken feedback loop: flaky test, under-constrained prompt, polluted context, or unflagged tool failure. The fix isn’t a smarter model; it’s identifying the loop signature in 30 seconds and breaking it with one targeted prompt. Below: the six loop shapes and the one-sentence constraint that breaks each.
Common causes
Ordered by hit rate, highest first.
1. Flaky test — pass / fail / pass / fail
The single most common trigger. A test uses Date.now(), network calls, randomness, or snapshot timestamps. Same code passes one run, fails the next. Agent sees red → edits → green → next run red → reverts → green → edits → red. Forever.
How to spot it: Stop the agent. Run the same command 3 times yourself without touching code. If you can reproduce green/red/green, the test is the problem.
2. Ambiguous prompt — agent oscillates between interpretations
“Fix the login bug” — but you have OAuth and magic-link. Agent fixes OAuth, magic-link test fails. Switches to magic-link, OAuth test fails. Bounces between mutually exclusive interpretations.
How to spot it: Look at the last 5 diffs. If they touch two different files in different features, each reverting the previous, the prompt is under-constrained.
3. Plan is wrong but agent refuses to replan
Agent committed to “change schema → change API → change UI.” Step 1 is impossible (table has data, can’t drop column). Agent stays on step 1 rewriting migrations forever instead of replanning.
How to spot it: Ask the agent “what’s your current plan + which step are you on?” If it says “still on step 1” for 5+ iterations, force a replan.
4. Context saturated with old errors
Long session, 80% of context is stale “previous failure” logs. Agent fits to old errors that no longer reflect current code state.
How to spot it: Open fresh session, paste current code, restate goal. If it succeeds first try, context pollution was the cause.
5. Agent ping-pongs between mock and real
Writes a mock to make test pass, next iteration decides mock is unrealistic, edits real implementation, original test breaks, goes back to mock. Loop.
How to spot it: Search the diff trail for jest.mock / vi.mock / MagicMock being added and removed across runs.
6. Tool call failed but agent didn’t notice
Bash returned a non-zero exit code. Agent parsed stdout as success and ran the same command again. Loop because the “failure signal” was invisible.
How to spot it: Check exit codes of last 3 tool calls. Non-zero but Claude didn’t say “command failed” = it missed the failure.
Shortest path to fix
Ordered by ROI. Step 1 + 2 break most loops in under a minute.
Step 1: Stop the agent, read the last 10 actions
Hit Esc / Stop. Then ask:
List the last 10 actions (file path + edit summary + command + exit code).
No commentary. Just a table.
90% of the time you’ll see two files alternating, the same line edited repeatedly, or repeated test runs without code progress. The pattern reveals the loop signature.
Step 2: Break with a one-sentence constraint
Match the loop signature to its constraint:
| Loop type | Constraint prompt |
|---|---|
| Edits same test expectation | ”Don’t touch the test. The test is right — change the implementation to match.” |
| Adds/removes mocks | ”Keep the real implementation. Delete all mocks and run the integration test.” |
| Rewrites migrations | ”Schema is frozen. Restart the plan but only touch the UI layer.” |
| Bouncing two files | ”You may only edit src/auth/login.ts. All other files are read-only.” |
| Same flaky test failing | ”Stop running this test. Mock the time-dependent dependency, then move on.” |
| Re-running failed command | ”The last command exited non-zero. Read the error, don’t re-run blindly.” |
Step 3: For flaky tests, stabilize the test first
Don’t let the agent loop on a flaky signal. Stabilize:
# Run 5 times — is it stable?
for i in {1..5}; do pnpm test -- --testNamePattern="login flow"; done
If 2+ fail, mock the source of nondeterminism:
// vitest setup
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-01'));
Then resume the agent with the now-stable test.
Step 4: Open a fresh session with full context
If nothing above works, the last lever is context reset:
1. Copy current file contents (not the diff).
2. New session.
3. Restart:
Goal: [one-sentence]
Current code: [paste full file]
Failing test/error: [paste verbatim]
Constraints: don't touch tests, only edit src/X.ts.
Print plan first; wait for approval before editing.
Fresh context isn’t polluted by old failure logs — usually succeeds first try.
Step 5: Set iteration caps
Prevent next loop. Add to CLAUDE.md:
## Agent behavior constraints
- Maximum 5 build/test iterations per task
- If failing after 3 iterations, stop and report
- Do not edit the same block in the same file more than 3 times
- After 3 retries of any tool call, stop and ask
Cursor uses .cursorrules, Codex uses AGENTS.md — same idea.
Step 6: Force replan on stuck plans
If the agent is on step 1 forever:
Stop. The current plan is failing. Don't continue.
Print:
1. Why step 1 isn't working
2. Two alternative approaches
3. Recommend one
Wait for my approval before continuing.
This forces the agent out of the failed plan instead of grinding on it.
Prevention
- Set iteration caps in CLAUDE.md so the loop has a built-in exit
- Isolate flaky tests into a
flaky.test.tsso agents never iterate on unstable signals - Force replan after 3 consecutive failed attempts on the same step
- Give explicit completion criteria — not “fix login” but “
pnpm test -- authexits 0” - Inspect the agent’s action trace, not just the final output — loops show in the trace
- When a loop appears, open a fresh session rather than recovering a polluted one