When an Agent Should Open a PR and When You Should Not Let It Push

Nate Holbrook

Nate Holbrook

September 30, 2026

When an Agent Should Open a PR and When You Should Not Let It Push

A contractor I host a private Gitea instance for messaged me on a Sunday: “Did you do something to the server? Two days of Priya’s commits are gone from the feature branch.” I had not touched anything. The server logs, which I keep because I have been burned before, told the story in about five minutes. At 23:40 on Saturday, a force push to feature/invoice-templates had replaced the branch with a version that did not include the last nine commits. The push came from the contractor’s own account, from his laptop.

He had asked a coding agent to “rebase the feature branch onto main and fix the conflicts.” The agent did, carefully, on his local copy, which had not been updated since Thursday. Then, to finish the job, it pushed. The push was rejected because the remote had moved. The agent read the error, recognised the standard fix for a rebased branch, and ran git push --force. Priya’s nine commits, pushed on Friday, were overwritten.

We got them back, because Priya still had them locally and the server’s reflog had not expired yet. But it made me rethink what I let agents do with remotes, both on the servers I run for other people and in my own repositories. The line I ended up drawing is not “agents may push” or “agents may not push.” It is about where they push, with what credential, and whether a human gate sits between that push and anything other people depend on.

Local edits are private. Pushes are not.

Everything an agent does in your working directory is, in a sense, a draft. You can review it, revert it, throw the branch away. Nobody else sees it until you decide.

A push changes that. The moment commits reach a shared remote, other people can pull them, build on them, and be affected by them. A force push goes further: it can remove work other people have already pushed. On most hosting setups, a push can also kick off automation that other people rely on: CI runs, notifications, bots that comment on linked issues.

So the question “should the agent push?” is really “should the agent be allowed to make changes that other people will see and depend on, without a human deciding first?” Framed that way, the answer is usually no, with one important exception.

A hand holding an envelope above a mailbox slot, pausing before dropping it in

The exception: a draft pull request

An agent pushing its own new branch and opening a draft pull request is one of the most useful things it can do, especially for tasks that run without you watching. The branch is new, so nothing of anyone else’s can be overwritten. The pull request is a draft, so it will not be merged or even formally reviewed until a human marks it ready. And the pull request is the natural place for review: the diff, the CI results, the agent’s description of what it did, all in one view your whole team already knows how to use.

For the contractor’s team, background agent tasks now end exactly that way: push to a new branch under agent/, open a draft PR with a description, stop. A person reads it, and either marks it ready for review, pushes fixes, or closes it. Nothing the agent does reaches a branch that humans are working on.

This is also, honestly, a better review experience than looking at an agent’s diff in an editor. CI runs the full test suite in a clean environment rather than on a laptop. Teammates can comment on specific lines. The history of the discussion is kept. Most of the arguments for opening a PR as a human apply equally to an agent.

When an agent should open a PR

After a few months, the team settled on a simple sense of when the agent-opens-PR path is right:

  • The task was run in the background or in a separate environment, and a human was not watching the work as it happened. The PR is how the work gets to a reviewer.
  • The change is self-contained and can be reviewed as a unit: a bug fix, a dependency bump, a small feature from a well-specified ticket.
  • Someone will actually review it soon. Draft PRs that sit for weeks rot, conflict with main, and add noise. If nobody will look at it this week, it is better not to open it.

And when it is not:

  • Exploratory work. If you are pairing with an agent to understand a problem, there is nothing to review yet. Opening a PR for a half-formed idea wastes reviewers’ attention.
  • Work that belongs in someone else’s branch. If the change should join an existing feature branch that humans are working on, the agent should not push there directly. It should hand the commits to the person who owns that branch, or open a PR targeting it.
  • Anything that needs a force push. If the only way to deliver the change is to rewrite an existing remote branch, a human should do that, after checking who else has pushed.

A homelab shelf in a basement with two used servers and a small network switch, status lights blinking

What the agent’s PR has to say

A draft pull request is only a useful gate if the reviewer can tell quickly what they are looking at. Agent-written descriptions tend to be fluent and overconfident: a tidy summary of what the code does, with nothing about what the agent was unsure of or did not check. The team’s rules now ask every agent PR description to include four things.

  • The task it was given, quoted or linked, so the reviewer can compare what was asked with what was built.
  • What it changed, file by file, in a sentence each.
  • What it ran: which tests, which commands, and their results, not “all tests pass.”
  • What it assumed or could not verify. This is the section that earns its keep. “Assumed the invoice template ID is unique per customer; did not find a constraint enforcing it” is exactly what a reviewer needs to see first.

A description in that shape takes the agent a few seconds to write and saves the reviewer the slowest part of the job, which is reconstructing what happened.

The controls that actually hold

Instructions in a rules file help, and the team has them: never force push, never push to a branch you did not create, only open draft PRs. But the incident happened because an agent followed a reasonable-looking path when a push failed. Instructions are a first line. The controls that actually hold live on the server and in the credentials.

Branch protection on shared branches. On Gitea, GitHub, GitLab, whichever you use, protect main and any long-lived shared branches: no direct pushes, no force pushes, merges only through reviewed pull requests. After the incident, we also enabled force-push protection on every branch matching feature/*. A force push to a protected branch is rejected by the server, no matter who or what sends it.

A separate identity for agents. When agents push from a background environment, they use a dedicated bot account, not a developer’s personal credentials. That account can push only to branches matching agent/* and open pull requests. It cannot push to main or feature/* at all. If an agent tries, the server says no. Scoping the token an agent works with matters just as much as scoping its branches.

Confirmations for push when it runs locally. When a developer uses an agent on their own machine with their own credentials, the agent’s command approvals are the last line. On the contractor’s team, any git push requires explicit approval, and git push --force and --force-with-lease are blocked in the tool’s configuration entirely. The contractor had allowed git * as a convenience, which is how the force push went through without anyone seeing it.

Keep the reflog and server logs long enough to matter. This is the self-hoster in me talking. The only reason we recovered cleanly is that the server kept reflog entries and push logs for the branch. If you host your own Git, check how long those are retained. If you use a hosted service, know how to find the push history for a branch before you need it on a Sunday.

If you really want the agent to push to an existing branch

Sometimes a developer wants an agent to push fixes to their own feature branch, the one only they work on. That can be reasonable. A few conditions make it safer:

  • The branch has a single owner, and that owner is the person running the agent.
  • The agent fetches before pushing and stops if the remote has commits it does not have locally.
  • Force pushing is still off. If a rebase is needed, the human does it.
  • The push goes to the developer’s branch, never to one that other people pull from.

The contractor’s mistake was that his agent treated a shared feature branch like a personal one. The rejection message from the server was the system telling it the branch had moved. The agent read that as an obstacle to get past, which is what --force is for.

What changed on the team

Priya’s commits went back onto the branch that Sunday afternoon. The team added force-push protection on every shared branch, created the bot identity for background agents, and changed the rules file to say, in plain words, that agents open draft PRs from new branches and do nothing else with remotes.

The agents still push every day. They push to branches nobody else uses and open pull requests that humans decide what to do with. That is the version of “let the agent push” I am comfortable hosting: plenty of useful work reaching the server, and nothing reaching the branches people depend on without a person deciding first.

More articles for you