← All posts

Gate the Boundary, Not Every Merge

I stopped making my AI coding agents ask before every merge. Now they auto-merge to dev and only stop at the door to production.

  • ai-agents
  • devops
  • git
  • automation
  • claude-code

For months my coding agents had a rule taped to their forehead: never auto-merge. Ever.

These are my DevFlow ADW agents — autonomous Claude Code sessions that pick up a task, write the code, open a pull request (a PR: a proposed change waiting to be folded into the shared codebase), and wait. The “never auto-merge” part meant they always waited for me to press the button, even on a tiny typo fix, even when every automated check had already passed.

I told myself this was about safety. It was really about one specific fear: an agent shipping something broken to production, the live code real users touch. But the rule didn’t say that. It said “never,” everywhere, for everything. So I became a human speed bump on changes that carried almost no risk.

The thing that fixed it wasn’t a smarter agent. It was noticing that “never merge” and “never merge to production” are completely different rules, and I’d been enforcing the strict one everywhere.

Here’s the shift. I already run a two-tier git setup: all work flows into a dev branch first — think of it as a staging kitchen where you can plate a dish, taste it, and throw it out if it’s wrong — and only later gets promoted to main, which is what ships. Nothing goes straight to main. So a mistake on dev costs almost nothing; a mistake on main costs real money and real trust.

Once I saw it that way, the permission boundary was obvious. It should sit at the branch tier, not on the word “merge.”

So now: any PR an agent opens targeting dev auto-merges the moment CI goes green (the automated tests pass) and CodeRabbit — the AI reviewer that reads the diff — comes back clean. No me. The agents move at full speed inside the staging kitchen.

The devmain promotion is the only step that still stops and asks. That’s the door to production, and I approve it by hand, in a session, looking at what’s about to ship.

Why this works: risk isn’t spread evenly across a workflow. It clumps at one boundary — the one where changes become irreversible and public. A blanket “always ask first” policy pretends every step is that step, which is why it feels both annoying and, oddly, unsafe. You get so used to rubber-stamping harmless merges that you stop reading them, and your attention is worn thin by the time the dangerous one arrives.

Branch-scoped auto-merge in the ADW workflow give me the detail

The gate lives in the PR’s base branch, not in a global agent policy. In GitHub Actions, auto-merge is enabled conditionally:

# .github/workflows/auto-merge.yml
on: pull_request
jobs:
  auto-merge:
    if: github.base_ref == 'dev'
    runs-on: ubuntu-latest
    steps:
      - name: Enable auto-merge (dev only)
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GH_TOKEN }}

if: github.base_ref == 'dev' is the whole trick — the job simply doesn’t exist for PRs targeting main. Branch protection on main requires a human approval, so the devmain promotion PR can never satisfy auto-merge even by accident. The agent’s autonomy is defined by where it’s pushing, and the rule is enforced by CI + branch protection, not by the agent choosing to behave.

If you run agents that open PRs, stop debating how much to trust them in the abstract. Separate your integration branch from production, then put your one human checkpoint on the promotion step and nowhere else. Let them run free where mistakes are cheap. Stand at the one door where they’re not.