Last March I left an agent running overnight. Came back to 4,200 tool calls and a $90 bill. It was "fixing" a failing test by editing the test, watching it pass, re-reading the file, deciding the file looked wrong, and editing it back. Forever. A perfect little hamster wheel powered by Opus.
If your Claude Code agent loops forever, it's almost never the model being dumb. It's you never defining what "done" means in a way the harness can enforce. Let me show you the three loop shapes I actually see in prod and how I kill each one.
The flip-flop loop
This is the hamster wheel above. The agent makes change A, observes a problem, makes change B that undoes A, observes the original problem, makes change A again. Each step looks locally reasonable. The model has no memory that it already tried this.
The tell: grep your transcript for repeated tool inputs. If the same edit with the same old_string shows up three times, you've got a flip-flop.
jq -r 'select(.type=="tool_use") | .input.file_path' run.jsonl | sort | uniq -c | sort -rn
If one file path has 40 edits, that's your loop. The fix isn't a better prompt. It's a stop hook that counts edits per file and halts the run when it crosses a threshold. I cap mine at 8 edits to a single file before the agent has to escalate to me. Eight is arbitrary. Pick your own. The point is the harness enforces it, not the model's good intentions.
The pause_turn loop
This one bites people using server-side tools — web search, code execution. The API runs its own internal loop, and when it hits the iteration cap it returns stop_reason: "pause_turn". You're supposed to re-send the assistant turn so the server resumes. A lot of hand-rolled loops instead treat pause_turn like end_turn, stop, and the user re-prompts "continue," which kicks off a fresh turn that pauses again.
The fix is dead simple but everyone gets it wrong:
if response.stop_reason == "pause_turn":
messages.append({"role": "assistant", "content": response.content})
# re-send — do NOT add a "Continue." user message
response = client.messages.create(model="claude-opus-4-8", messages=messages, tools=tools)
Do not append a fake user "Continue." message. The API detects the trailing server_tool_use block and resumes on its own. Adding the user turn confuses it. And set a max_continuations counter — 5 is fine — so a genuinely stuck turn can't spin.
The context-rot loop
The nasty one. The agent runs long enough that its early context gets stale, it forgets it already solved subtask three, re-reads the same files, re-derives the same plan, and burns tokens re-establishing state it already had. It doesn't flip-flop on a single file. It just never converges, because every turn it's half-rebuilding its own memory.
You catch this in the usage numbers, not the transcript. If input_tokens keeps climbing while actual progress flatlines, the model is paying to re-read its own history. On Opus 4.8 with the 1M window this can run for ages before it falls over, which makes it worse — it doesn't crash, it just quietly bills you.
Two fixes. First, turn on compaction (compact-2026-01-12 beta) so the API summarizes old context server-side instead of dragging the full transcript. Critical gotcha: you must append the full response.content back each turn, not just the text — the compaction blocks live in there, and if you strip to text you silently lose the compaction state and the rot comes right back. Second, give the agent a memory file and tell it to write progress there. A scratchpad on disk survives the loop; conversation history doesn't.
The guardrail that catches all three
Loop-specific fixes are good, but the backstop that has saved me the most money is brain-dead: a wall-clock and tool-call ceiling enforced outside the model. I run the agent under a watchdog. If it exceeds N tool calls or M minutes, the watchdog kills it and dumps the last 20 events. The model gets no vote.
Why outside the model? Because every loop I've described is the model believing it's making progress. You cannot prompt your way out of "the agent is confidently wrong about being done." The hook has to live in the harness — a Stop hook in Claude Code, or a counter in your own agent loop. Token countdowns you show the model don't count; on the long-horizon models that countdown actually makes it anxious and it'll suggest starting a new session instead of finishing.
Here's the thing nobody tells you when you start building agents. Your prompt engineering caps out fast. The reliability comes from the boring stuff around the loop — counters, watchdogs, edit caps, a hard max_continuations. The model is the easy part. The leash is the job.
Write the leash first. Then let it run overnight.
