← BlogGuides

Your coding agent re-fixes code that was already fixed

AI agents patch already fixed code in 35 to 65 percent of stale tickets. What a work item must carry so a fresh session tells done from open.

The Taskfolk team

10 min read11 views

XLinkedIn

An agent run leaves a four hundred line diff on a ticket. The bug that ticket describes was fixed nine days ago by someone on your team, in a commit that has been on main since. Tests pass. They passed before the run too. Now a human has to read the whole diff to work out that it changes nothing anyone wanted changed.

That run is not a fluke. In March 2026 the SRI Lab at ETH Zurich published FixedBench, 200 tasks where the bug has already been patched, followed on 8 May by Coding Agents Don't Know When to Act (both fetched 27 July 2026). Five models across four harnesses made unnecessary edits to already correct code in 35 to 65 percent of cases. The authors put it plainly: "at least one in three stale issue reports results in spurious edits to working code."

Almost every write-up of this lands on the agent's own configuration: reproduce before you patch, put a rule in the config file, install a memory server. That advice is real, and it is half the job. The half I have not seen written down is what the ticket itself has to carry.

The measurement, and why it is worse than it sounds

FixedBench takes 200 SWE-bench Verified instances, commits the golden patch from the original pull request, then hands the agent the original issue text. The correct answer is an empty patch. Editing tests or docs is allowed. Touching executable code is a failure.

Model and harness Correctly left the code alone
GPT-5.3 Codex in Codex 68%
Sonnet 4.6 in Claude Code 65.0%
GPT-5.4 mini in Codex 60.5%
Qwen3.5 122B in Qwen Code 50.3%
Gemini 3 Pro in Gemini CLI 36.5%

Treat the top row as approximate: the blog chart shows 68 percent for GPT-5.3 Codex, while the paper text says the best models do not clear 67 percent. The figure with a published interval is Sonnet 4.6 at 65.0 plus or minus 6.6.

Those are the good conditions. The paper's best case has "the Git history available with the last commit resolving the issue" plus a fully set up environment, which it calls "a realistic setting where the agent is part of a deployment pipeline." The resolving commit was the newest entry in the log, and between a third and two thirds of runs patched anyway. Remove the .git folder and Sonnet 4.6 falls to 50.0 percent.

Why it edits when the fix is one git log away

The obvious theory is that it never looked. Partly true. The authors read 50 Sonnet 4.6 traces by hand to define ten behaviour categories, then used a model as judge across the rest, at 80.5 percent precision against the humans. Traces ending in abstention had checked the git history 63.8 percent of the time. Traces ending in an edit, 31.4 percent.

Knowing does not stop it, and that is the part that should change what you do. Under a reproduce first prompt, the agent worked out the bug was already fixed 79.5 percent of the time. Under a prompt that also offered abstention as an outcome, 81.0 percent. Practically identical.

Correct abstention was 65.5 percent against 80.5 percent. Same understanding, wildly different behaviour. In the authors' words, "the key issue is not the agent's ability to reproduce an issue and realize it is already resolved, but what it believes its success criteria are."

Failing traces have a signature. They modify code unrelated to the reported issue 87.1 percent of the time, and 47.1 percent apply changes that do not meaningfully alter behaviour. That second number is a no op diff seen from the inside. The agent has no way to report success, so it produces a change instead.

flowchart TD
  A["Fresh session opens a stale ticket"] --> B{"Does the item name the resolving commit?"}
  B -->|"No"| C["Agent infers state from the repo"]
  C --> D{"Reproduces the defect first?"}
  D -->|"No"| E["Patches working code"]
  D -->|"Yes"| F["Usually realises it is fixed"]
  B -->|"Yes"| F
  F --> G{"Is a no change outcome available?"}
  G -->|"No"| E
  G -->|"Yes"| H["Reports back with evidence"]

Telling it to reproduce first does not work

Most advice here is wrong, including mine before I read the numbers. The reproduce first prompt on its own moved Sonnet 4.6 from 65.0 to 65.5 percent, a p value of 1.0. On GPT-5.4 mini it went the other way, 60.5 down to 47.5. Thirteen points worse than saying nothing. The prompt that worked framed no change as a completed task: 80.5 percent for Sonnet, 88.5 percent for GPT-5.4 mini.

That prompt breaks in the other direction. On the partial task, where a wrong patch is already applied and the agent should finish the job, the same instruction pushed both models into passivity. Sonnet made a meaningful edit in 18.7 percent of those cases, GPT-5.4 mini in 6.4 percent. The paper is blunt: "it can suppress action bias on fixed code at the cost of inducing passivity on broken code."

More thinking does not rescue it. Across four reasoning effort levels GPT-5.4 mini moved from 61.5 to 65.8 percent, inside a confidence interval of roughly seven points. Action bias, the authors conclude, "is not a failure of deliberation that more compute can overcome, but rather a failure of task framing."

So the ceiling on prompting sits between one bad run in five and one in nine, bought by making the agent worse at broken code. Past that, you change what the agent is reading.

What the work item has to carry

Open any ticket in your tracker and ask whether a fresh session, with no memory of your team, could tell "not done" from "done and stale" without asking a human. For most tickets it cannot, and the missing pieces are boring.

What the item carries What a fresh session concludes What happens without it
The resolving commit, on the item The fix is in this sha, by this author, at this time The agent guesses from repo state
A terminal state for fixed and verified, separate from cancelled Someone verified the fix after it landed Closed and abandoned look identical
A hand off note written when work stopped What was tried, what was left, why The code survives, the reasoning does not
An acceptance step naming the reproduction What to run before editing a file Any green suite reads as confirmation
A reachable no change outcome Reporting is a finished task Only a patch counts as done

The first row is the one people assume they already have. Check it. GitHub's closing keywords are interpreted, per its own docs fetched 27 July 2026, only when the pull request targets the default branch. Target a release branch and "these keywords are ignored, no links are created, and merging the PR has no effect on the issues." A squash merge whose message drops the key leaves nothing either. A team merging into develop can ship unlinked fixes for years without noticing, because until now the only readers of those tickets remembered.

The last row is the one nobody builds. If the only terminal state on your board is Done, and Done means shipped work, an agent that finds nothing to fix has no move that counts. So it writes the patch.

Wiring it up in Taskfolk

Start with the boundary. Taskfolk never reads your repository or your diffs. It stores what a person or a process told it, and everything below makes that record worth reading.

Commit references land on the issue through the GitHub push webhook. A message containing #WEB-118 anywhere, or WEB-118 at the very start, links that commit with its sha, message, author and timestamp, and writes an append only activity row. Setup is a per project connection (how to connect GitHub). The reference lives on the ticket, so deleting the branch does not take the evidence with it.

Your coding agent re-fixes code that was already fixed - issue linked commits The Linked commits card on issue WEB-118, listing two commits with short sha, message, author and date, under the issue title and status.

BASE=https://taskfolk.ai/api/v1/workspaces/acme
curl -H "Authorization: Bearer tfk_live_a1b2..." \
  "$BASE/projects/WEB/issues/WEB-118/commits"

Next, split the terminal states. Taskfolk keeps seven fixed status categories under the board and lets you name any number of columns on top, each mapped to one category. "Fixed and verified" maps to done, "No change needed" maps to cancelled, and both are real columns. Reports and the open or closed split still work because they read the category, not the name (custom workflow statuses, and the wider bug tracking workflow).

Your coding agent re-fixes code that was already fixed - board terminal columns A board with two closing columns, Fixed and verified in green and No change needed in red, each holding cards.

Allowed transitions are the enforced control, and they run on every write path: the board, bulk edits, automation actions and the REST API. Give Backlog two destinations, To do and In progress, and an agent call that jumps a card straight to No change needed comes back rejected with both status names in the error. The per column WIP limit is not that. You can set it, but no write path reads it, and the count badge and red header on the board are a view toggle. Nothing refuses a card.

Your coding agent re-fixes code that was already fixed - status transition rules Project settings, Board statuses card open with one status expanded to show its allowed transitions and mapped category.

The acceptance step from the table is a text custom field. Define one called Reproduction, scope it to bugs, and every bug carries the command to run before anyone edits a file (custom fields in issue tracking). Marking it required does not stop someone filing a bug without it. It only stops them clearing the value once it is set, which is weaker than a gate.

Now the outcome the research says is missing. Assigning an issue to an agent creates a pending session the agent claims when it starts. When it finds nothing to fix, the finished shape is a comment carrying evidence, a session moved to review, and a card in the no change column. That leaves no branch and nothing in the review queue.

curl -X POST -H "Authorization: Bearer tfk_live_a1b2..." \
  -H "Content-Type: application/json" \
  "$BASE/projects/WEB/issues/WEB-118/comments" \
  -d '{"body_md":"No change needed. Reproduction passes on main at 4f0c9ae. Fix landed in 2b71d3c."}'
await fetch(`${BASE}/projects/WEB/issues/WEB-118/comments`, {
  method: "POST",
  headers: {
    Authorization: "Bearer tfk_live_a1b2...",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ body_md: "No change needed. Passes on main at 4f0c9ae." }),
});
requests.post(
    f"{BASE}/projects/WEB/issues/WEB-118/comments",
    headers={"Authorization": "Bearer tfk_live_a1b2..."},
    json={"body_md": "No change needed. Passes on main at 4f0c9ae."},
)
{
  "id": "0199f3c1-6b2a-7c41-9f0d-2c8a1e5b7d33",
  "issue_id": "0199e07a-4c11-7a92-b3d6-8f1c2a4e9b05",
  "author_id": "0199c4d8-9b30-7e15-8a71-5d0b3f6c2e44",
  "body_md": "No change needed. Passes on main at 4f0c9ae.",
  "body_html": "<p>No change needed. Passes on main at 4f0c9ae.</p>",
  "created_at": "2026-07-26T09:14:22.000Z",
  "updated_at": "2026-07-26T09:14:22.000Z"
}

Then move the card. POST .../issues/WEB-118/transition takes a status category, so {"status":"cancelled"} puts the issue in that project's default cancelled column; to land on a specific named column, PATCH the issue with its status_id. Both run the same transition check a human board drag does. The agent authenticates with its own API key, so the comment and the status change are attributed to its member identity, which is what the audit trail is built from.

One note on checking the work, covered properly in managing a team of AI agents. A session reaching review or done with no attributed activity row and no comment on its issue is flagged unverified. That is a heuristic, not proof, and one comment defeats it. Here it still helps: an agent that really found nothing clears the flag by writing the report.

Where this stops

Taskfolk cannot detect that a fix already landed. It has no view of your repository, branches or diffs, and never reads them.

GitHub is the only repository integration. No GitLab, no Bitbucket, no Azure DevOps.

The commit reference appears only when the issue key is in the commit message. Squash merge with a generated message, or put the key only in the pull request title, and you get an empty card plus a false sense of coverage. The webhook listens to push events, so it links commits, not pull requests.

Taskfolk also does not run your agent. It delivers triggers and stores the session record. The decision to abstain happens in a runtime you own, which is why the prompt half stays your job.

The bill for ignoring this

The wasted run is the cheap part. The expensive part is what happens to the diff.

Alam, Mondal and Roy analysed 8,106 agent authored fix pull requests from the AIDev dataset in a study prepared for MSR 2026 (29 January 2026, fetched 27 July 2026). 65.0 percent merged, 26.1 percent closed without merging. They hand reviewed 326 of the closed ones over roughly 100 person hours. The most common failure reason was that the issue had already been resolved by a different pull request, at 22.1 percent. Another 8.0 percent were deprioritised or obsolete by the time anyone looked.

Then the multiplier. Xu, Subramanian and Karthik looked at 33,596 agent pull requests across 2,807 repositories and found 79.4 percent open concurrently with another agent pull request in the same repository, rising to 95 percent within a week (7 July 2026, fetched 27 July 2026). A ticket does not go stale over months. It goes stale over an afternoon, because your own second agent already fixed it. That is the ordinary case behind a shared backlog for AI coding agents and coordinating agents with issue status as handoffs.

Each run costs provider tokens, a branch, a CI run, and a human read. The read is the part you cannot automate away, and it is spent on diffs that should have been empty.

Pick one bug you closed last week. Read only what is on the ticket, the way an agent would. If you cannot find the commit that fixed it, or tell whether anyone verified the fix, you already know what your next agent run will do with it.

Frequently asked questions

Why does my AI agent rewrite code that was already fixed?

Because a patch is the only outcome it counts as success. ETH Zurich's FixedBench found agents editing already correct code in 35 to 65 percent of cases, and trace analysis showed they often knew the bug was fixed and edited anyway. Give the run a defined non edit outcome and the rate drops sharply.

Does telling the agent to reproduce the bug first fix the problem?

Not on its own. In the FixedBench study a reproduce first prompt moved Sonnet 4.6 from 65.0 to 65.5 percent (p = 1.0) and made GPT-5.4 mini worse, 60.5 down to 47.5. Only the prompt framing abstention as a successful outcome helped, and it then made both models too passive on genuinely broken code.

Can Taskfolk tell that a fix already landed?

No. Taskfolk never reads your repository or your diffs. It stores the commit references that arrive from the GitHub push webhook when a commit message contains the issue key, plus whatever people and agents write on the item. That record is what a fresh session reads.

What should a closed ticket contain so an agent does not redo the work?

The resolving commit linked on the item, a terminal state that separates fixed and verified from cancelled, a note describing where work stopped, and an acceptance step naming the reproduction to run before editing anything. Add a status the agent can move the card to when it finds nothing to fix.

Related reading

Add a comment

Start the conversation.