Dashboard

Stop Your AI Coding Agent From Committing to Main

Telling an agent not to push to main works until it doesn't. Here is the branch protection rule, pre-push hook, and tool-scoping setup that makes the mistake physically impossible instead of just discouraged.

Steve Jefferson
Steve Jefferson
Developer Advocate
17 September 20261 min read

Stop Your AI Coding Agent From Committing to Main

The fix is not a nicer instruction in your prompt. It is branch protection on the remote, a pre-commit hook that refuses the push, and an agent configuration that removes the ability to target main at all. Telling an agent "never commit to main" in plain English works until it doesn't, usually at the exact moment a merge conflict or a failing test makes the agent decide the instruction was optional. Below is the setup I actually run, in order of how much it costs to set up.

Why the instruction alone fails

An AI coding agent follows a system prompt the same way it follows any other context: as a strong prior, not a hard constraint. Under normal conditions "don't push to main" holds. Under pressure, when a test is failing and the agent is trying to get to green, or when it has lost track of which branch it's on after a rebase, the instruction competes with the agent's drive to finish the task successfully. The instruction usually loses, because nothing in the agent's tool access actually prevents the push, it just asks nicely.

The reliable fix moves the constraint from the prompt into something the agent's tools cannot override: the git remote itself.

Layer 1: branch protection on the remote

This is the one that actually holds even if every other layer fails. On GitHub, GitLab, and Bitbucket, you can require pull requests before merging to main and disallow direct pushes, including from tokens with write access. Set this up once per repository:

  • GitHub: repository Settings, Branches, add a rule for main, enable "Require a pull request before merging" and "Do not allow bypassing the above settings", including for admins and for the token your agent authenticates with.

  • GitLab: Settings, Repository, Protected branches, set main to "No one" for Allowed to push, "Maintainers" or narrower for Allowed to merge.

  • Bitbucket: Repository settings, Branch permissions, restrict main to require a pull request and disallow direct writes.

If your agent authenticates with a personal access token or an app installation token, protection rules apply to that token the same way they apply to a human, as long as "bypass" is explicitly disabled for admins and tokens. This is the step people skip, and it is the one that matters most.

Layer 2: a pre-commit or pre-push hook

Server-side protection stops the push from landing, but a local hook stops the attempt earlier and gives the agent an immediate, machine-readable error it can react to instead of a rejected push it has to interpret. A minimal pre-push hook:

bash
#!/bin/sh
protected_branch='main'
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" = "$protected_branch" ]; then
  echo "Direct push to $protected_branch is blocked. Create a branch and open a PR."
  exit 1
fi

Save this as .git/hooks/pre-push and make it executable. It runs locally, so pair it with the remote-side rule rather than relying on it alone, a hook lives in an untracked directory and a fresh clone or container won't have it unless your setup script installs it.

Layer 3: scope the agent's tool access

The most direct fix, where your tooling supports it, is to not give the agent a tool call that can push to a protected ref in the first place. Several agent runtimes let you allowlist git subcommands or wrap git in a script that inspects the target branch before executing. If the agent's shell access goes through an allowlisted command layer, add a check there rather than trusting the agent to self-police:

bash
git() {
  if [ "$1" = "push" ] && git rev-parse --abbrev-ref HEAD | grep -q '^main$'; then
    echo "Blocked: agent attempted push from main" >&2
    return 1
  fi
  command git "$@"
}

This is more setup than the other two layers and only worth it if you're running agents unattended for long stretches, where you want the failure caught before it generates an error message the agent has to reason about.

Layer 4: tell the agent what to do instead

Once main is actually protected, the instruction in your system prompt or agent config stops being a safety mechanism and becomes a workflow description, which agents follow far more reliably because there's no longer a way to fail into the bad outcome. State the actual desired flow explicitly: create a branch named for the task, commit there, open a pull request, and stop. Give it the branch naming convention you want so it doesn't invent one, and tell it explicitly not to merge the PR itself if you want a human or CI gate in between.

What this looks like when it works

With all three technical layers in place, an agent that tries to push to main gets rejected at the hook (fast, local, cheap) or at the remote (authoritative, applies even if the hook is missing). Either way it receives a clear error rather than a silent success, and a competent agent will read that error and switch to the branch-and-PR flow on its own, especially if your system prompt already told it that's the expected path. You end up needing the plain-English instruction less, not more, because the technical guardrail carries the weight the instruction used to carry alone.

The same logic extends to other blast-radius limits worth setting up alongside this one, like keeping an agent from touching your production database, being deliberate about how much of your codebase an agent can see, and checking whether an agent can leak your api keys through a careless commit. Guardrails compound: an agent that can't see your database credentials and can't push to main has two fewer ways to cause an incident while you're not watching. For the wider set of guardrails worth having in place, see the AI coding tools overview.

FAQ

Will branch protection break my agent's workflow?

Only if the agent's only configured flow is direct commits to main. Once it's told to branch and open a PR, protection rules make that the only path that succeeds, which is the point.

What if I want the agent to merge its own PRs after CI passes?

That's a separate decision from blocking direct pushes, and a riskier one. Keep the branch protection rule in place either way, and if you do allow auto-merge, gate it strictly on CI status rather than the agent's own judgment that the change looks fine.

Does this apply to solo projects too?

Yes, arguably more so, since a solo setup often has no human reviewing the agent's commits at all. The remote-side rule takes minutes to set up and costs nothing in a repo with one contributor.

My agent keeps saying it will use a branch and then doesn't. What's wrong?

That's the exact failure this post describes: an instruction competing with the agent's drive to finish the task. Don't fix it with a stronger instruction, fix it with a rule the push physically cannot succeed against.

How did this land?

About the author

Steve Jefferson
Steve Jefferson

Developer Advocate

Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.