When agents shouldn't open the pull request
Some changes belong with a senior owner. Auth, payments, crypto, irreversible migrations, infra with blast radius and untested code, and how to encode that in repo instructions and branch protection.
- SMSimran MalhotraCo-founder
4 min read
Most agent policy discussions start with people. Which engineers get agent access, whether juniors should have it, how many PRs an agent may open in a day. The more useful line is drawn around the work. Some changes are cheap to get wrong and cheap to review. Others fail quietly, cost money or can't be undone, and for those, who wrote the first draft matters much less than who owns the result.
We sort tasks into two groups. A few stay away from agents entirely, apart from reading and explaining. The rest can go to an agent only when a senior owner approved the plan before any code was written and reviews the result personally.
The task classes
Authentication and authorisation come first. Session handling, permission checks, tenant isolation and token lifetimes fail silently: everything works in the demo, and the bug is that someone can see a record they shouldn't. Tests on these paths are usually thin, so green CI tells you little. An agent can draft a change here behind a senior owner. It shouldn't be the one deciding the shape of the change.
Payments and anything that moves money. Refunds, payouts, pricing rules, currency rounding, retries against a payment provider. A duplicate charge from a retry without an idempotency key reaches customers before it reaches you. Same rule: senior owner, plan first, line-by-line review.
Cryptography stays with people. Key handling, signing, token generation, anything that picks an algorithm or a mode. Code here can look correct, pass every test you have and still be broken in ways tests don't catch. We keep agents to reading and explaining in this area.
Irreversible migrations. Dropping a column, a backfill that overwrites values, deleting data, changing a key on a large table. If the rollback plan is "restore from backup", an agent doesn't open that PR. It can write the reversible parts, like the new column, the dual write and the new read path. A person writes and runs the destructive step.
Infrastructure with a blast radius. IAM policies, network rules, DNS, production Terraform, the CI workflow that deploys. Agent PRs here tend to ignore realities that live outside the repo: an unpinned base image, a new service that needs config nobody wrote down, a suggestion to switch off SSL verification to get past an error. Infra changes from agents go through the platform owner with the plan output attached.
Anything without tests. With no suite around the code there's no oracle. The agent writes tests that confirm whatever it built, and the reviewer has nothing to compare against. This is the one class that can move. Let the agent write characterisation tests first, have a person check they describe today's behaviour, and the area becomes fair game.
In our own workflow, issues touching security, money, user data or production carry a risky label, which turns on a second review by a different agent by default. Whatever the label, agents never approve, ship or merge.
Write it where the agent reads it
The first place for the policy is the instruction file the agent already loads, AGENTS.md or CLAUDE.md. Plain language, with paths and owners:
## Changes that need a senior owner
Do not open a PR that changes these paths. Stop, explain what you
would change and why, and ask for the owner.
- src/auth/, src/session/: owner @platform-security
- src/billing/, src/payouts/: owner @payments
- db/migrations/: no destructive steps (DROP, DELETE, overwriting backfills)
- infra/, .github/workflows/: owner @platform
- lib/crypto/: read and explain only
If the code you need to change has no tests, write characterisation
tests first and stop for review before changing behaviour.That covers the common case, where the agent follows its instructions. It does nothing when it doesn't, or when someone tells it to go ahead anyway. Instructions are requests. The rule itself has to live somewhere the agent can't edit.
Enforce it where it can't be skipped
Three layers do most of the work. A CODEOWNERS file assigns each protected path to a team. Branch protection, or a repository ruleset, requires review from code owners, requires approval of the most recent push so a late commit can't slip in after sign-off, and allows nobody to bypass it, admins and bots included. And a required CI check fails whenever a bot-authored PR touches a protected path, so the PR never reaches a reviewer looking routine.
# .github/workflows/agent-path-guard.yml
name: agent-path-guard
on: pull_request
jobs:
guard:
if: github.event.pull_request.user.type == 'Bot'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fail on protected paths
env:
BASE_REF: ${{ github.base_ref }}
run: |
changed=$(git diff --name-only "origin/$BASE_REF...HEAD")
protected='^(src/(auth|session|billing|payouts)|lib/crypto'
protected="$protected|db/migrations|infra|\.github/workflows)/"
if echo "$changed" | grep -E "$protected"; then
echo "Agent PR touches a protected path. Needs a senior owner."
exit 1
fiTwo details matter. A pull_request workflow runs the version of the file in the PR, so the guard's own path has to sit under CODEOWNERS too, or an agent could edit the check out of existence. And the check keys on the PR author being a bot, which only works if agents push under their own bot identity. If yours push through engineers' personal accounts, fix that first. A separate identity also makes agent work easy to measure.
Some teams add a pre-edit hook in the agent tool that refuses writes to these paths. It's a useful early stop that saves a wasted session. It runs on the engineer's machine, though, so treat it as a convenience and keep the real enforcement on the server.
Start with the list. Name the five directories where a bad change would page someone at night, put them in CODEOWNERS this week, and build the instruction file section and the CI guard from that same list so the three never drift apart.