RSS Amplifier

Serghei's Blog · Apr 12, 2026

What Breaks When You Run Coding Agents Unsupervised

0
Sign in to vote or save

Serghei Iakovlev · Serghei's Blog

Coding agents can write code. That capability is real. Point Claude Code or Copilot at a well-scoped issue and you get a working pull request most of the time. Now try running five of them unattended. A class of failures appears that has nothing to do with how well the agent writes code. I spent the past month building a Go project. Agents handled issue implementation; I handled architecture and review. 242 issues closed, 190 PRs merged, 18 releases shipped in just over three weeks. The velocity was real, but so were the failure modes. Here are the five that kept recurring.

The silent exit-zero#

You dispatch agents to a batch of issues before heading out. You come back to find every one exited code 0. You check the repos. Some have branches with real changes. Others have nothing. No branch, no diff, no PR.

What happened: the agent subprocess crashed during startup. Malformed MCP config, missing binary, a permissions error that the runtime swallowed. The crash happened before the agent loop started, so the process exited 0. Zero output tokens. Zero files touched.

If you are watching a terminal, you catch this instantly. Nothing happened. But the entire premise of running unsupervised is that you are not watching. Exit code 0 is the only signal you get, and it lied.

Do not trust exit codes alone. An agent that exits 0 but produces zero output tokens and writes no result event did not succeed. It silently failed. Treat it as a failure and retry. The distinction between “process produced output” and “process exited without error” is the first thing that breaks when you stop watching.

CI breaks into the void#

Agent pushes a branch. Agent exits. CI runs. Tests fail. Nothing happens next. The agent is gone. The branch sits with a red check until a human notices, maybe days later, maybe never.

This is the gap between “agent wrote code” and “code actually works”. The agent’s job is not done when it pushes. It is done when CI passes. The agent can run tests locally — and it should — but CI is a different machine with a clean checkout. It catches what the agent’s local run misses: the full test suite instead of the subset the agent chose, integration tests that need credentials or services the sandbox does not have, and environment differences the agent never knew existed. Without a feedback loop from CI back to the agent, you are paying for sessions that produce broken branches, and a human still fixes them by hand. Several of the PRs that eventually merged in my project needed two or three CI-fix iterations before they passed. Without the feedback loop, those would have been dead branches.

The fix: after the agent exits, poll CI status. On failure, fetch the logs, feed them into a continuation prompt, let the agent try again. This turns a dead-end branch into an iterative fix cycle. The agent sees the actual compiler error or test failure and can diagnose the problem.

The detail that cost me the most time: CI stays pending for minutes after a push. Polling at ten-second intervals burns API rate limits. Exponential backoff on pending status cuts requests from roughly 120 to 15 per twenty-minute CI run. Without it, you hit GitHub rate limits before the first CI run finishes.

Reviews addressed to nobody#

Agent creates a PR. Reviewer reads it, requests changes, leaves three comments about error handling and test coverage. The PR sits there. The agent is long gone. Nobody addresses the feedback.

Code review is the one workflow step where a human actively engages with the agent’s output. The reviewer did exactly what they were supposed to do. But the system has no path from “reviewer left comments” back to “agent addresses them”. When a human developer gets review comments, they see a notification and push a fix. Agents do not check notifications.

The fix: detect when a reviewer requests changes on an agent-created PR, extract the comments, and route them into a continuation session. The agent sees the reviewer’s feedback and pushes an update. This closes the one loop where a human already invested time reviewing, and it is the loop most often broken.

The non-obvious problem is debouncing. A thorough reviewer leaves five comments in two minutes. Without debouncing, each triggers a separate agent session. Five agents racing on the same branch, each addressing a subset of comments, each force-pushing over the others. Collapse rapid-fire events into a single dispatch.

State that dies with the process#

You have a script dispatching agents to eight issues. Your machine reboots. Or the script crashes. Or you ctrl-C by accident. Which issues were mid-flight? Which pushed branches? Which were on their third retry? You have no idea. You re-run everything. Issues that already exhausted their retry budget get five fresh attempts. Issues that were nearly done restart from scratch.

This is the most predictable failure on the list and the one most people skip. Agent dispatch scripts grow organically. They start as five lines of bash. By the time state management matters, the script is load-bearing and nobody wants to rewrite it. But any process that tracks retry counts, in-flight assignments, and backoff timers in memory will lose all of them on restart. Across 190 merged PRs and 18 releases, process restarts were not rare. They were routine.

The fix: persist dispatch state to disk. I used SQLite in WAL mode for concurrent reads during agent execution, but the mechanism matters less than the principle. If the process can be killed, the state must not live only inside it.

Even with persistence, edge cases bite. I had a CI-fix task marked as “scheduled” in the database before the agent actually launched. The machine restarted in that gap. When the orchestrator came back, it saw the record said “already handled” — but nothing had run. A 200ms timing window between writing the record and starting the process, and it silently dropped one task forever.

No way to say “I need help”#

Agent hits a problem requiring human judgment. Ambiguous requirement. Dependency conflict with no clear resolution. A design decision it has no context to make. The agent exits normally, or it takes a guess that makes things worse. You see “agent finished” and move on, or you restart it. It hits the same wall. This loops until the retry budget runs out, and you discover a week later that the issue needed a one-line clarification from the product owner.

The root problem: agents have exactly two output states. They finish, or they crash. There is no signal for “I need a human to make a decision”. From your perspective, a stuck agent looks identical to an agent that needs one more try.

The fix: give agents an escalation channel. I used a file-based approach (agent writes blocked or needs-human-review to a known path, the infrastructure reads it after each turn), but the mechanism could be anything: a special exit code, an API call, a webhook. What matters is that the channel exists and your infrastructure respects it by stopping retries until a human acts.

The design constraint: absent or unrecognized files degrade to normal behavior. An agent that knows nothing about the channel works fine. It just cannot ask for help. The protocol must be additive, not mandatory.


None of these failures involve code generation quality. The agent writes correct code in every case, when it runs at all. The failures are all in the feedback infrastructure: knowing whether the agent actually did something, routing CI results back, routing review comments back, surviving restarts, hearing when the agent is stuck. Launching an agent is one function call:

cmd := exec.CommandContext(ctx, "claude", "-p", prompt, "--output-format", "stream-json")

That is the easy line. Everything that breaks is around it.

I built an open-source orchestrator called Sortie because I kept hitting these problems. Single Go binary, SQLite persistence, adapter interfaces for different agents and trackers. Apache-2.0. But the failure modes exist whether you use an orchestrator, a bash script, or a CI pipeline with claude --print. If you are scaling from one agent in a terminal to several running unattended, build the feedback infrastructure before you build the dispatcher. The agent will be fine. Everything around it will not.

Read the original on blog.serghei.pl

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.