Get started Dashboard
AI in production · Updated
Part 2 of AI x Production: coding agents, MCP and autofix

How to keep AI coding agents from breaking production

Explore with AI

An AI coding agent reaches production in two ways: through the code it merges, and through the actions it takes with its own credentials. Close the second path with a separate, read-only identity and human approval for every write. Gate the first with branch protection the agent can't bypass, required checks that really block, and a review of each change against the live system it deploys to.

On this page

To keep an AI coding agent from breaking production, control the two routes it has there. One is the code it merges. The other is the commands and tool calls it runs with its own credentials. Give the agent its own identity with read-only access to anything live. Make every change land as a pull request. Let branch protection, required checks and a person decide the merge. Then check each change against the production system it lands on, and make a bad deploy quick to spot and quick to undo.

You don’t need a new tool to start. GitHub branch protection, a CODEOWNERS file, a scoped credential and a written rules file for the agent cover most of the risk today. This page walks through each control, the settings that matter, and the gaps agents slip through when a control is only half set up.

What it means for an AI coding agent to break production

An AI coding agent is a language model with tools. It reads your repository, runs shell commands, edits files and opens pull requests. Through MCP servers and other integrations it can also call APIs, query databases and change infrastructure. Breaking production means one of two things:

  1. Through the merge. The agent writes a change that passes review and CI, deploys, and then hurts live traffic. Typical examples: a migration that locks a busy table, code that reads an environment variable nothing provisions, an endpoint removed while callers still use it, or a dependency whose package name the model made up.
  2. Through its own actions. The agent runs something against a live system directly: a write to a production database, a cloud config change, a push straight to main, a deleted resource.

The bugs agents write look like the bugs people write. The difference is how many changes arrive and how fast. An agent keeps producing diffs for as long as it runs, so reviewers get more to read, and each diff comes with less of the author’s reasoning behind it. So put your controls where speed can’t skip them: in credentials the agent doesn’t hold, and in merge rules enforced on the server.

Close the direct path: the agent’s own credentials

Start here. A merge gate is useless if the agent can reach the database without going through it.

  1. Give the agent its own identity. Use a bot account or GitHub App and a dedicated cloud role. Never hand it your personal token. Audit logs then show which actions came from the agent, and branch protection can treat it as a normal, non-admin contributor.
  2. Make that identity read-only on anything live. Reading logs and metrics and describing resources is enough for most agent work. Grant no deploy rights and no write access to production data stores.
  3. Keep production secrets out of the agent’s environment. Don’t load the production .env into the sandbox or workspace where the agent runs. If a credential appears in its context, it can end up in generated code or commit history.
  4. Require a person’s approval for every irreversible action. That covers deletes, migrations run by hand, infrastructure changes and anything that touches customer data.
  5. Scope each MCP server like an IAM policy. Expose only the tools the task needs, require authentication on external connections, and log every tool call. The MCP config file is a permission grant, so review it the way you review IAM policies.
  6. Treat everything the agent reads from outside as untrusted. Issue text, web pages, READMEs in third-party packages and MCP responses can all carry instructions. Prompt injection tops the OWASP Top 10 for LLM applications. Give any agent that reads untrusted content read-only, least-privilege access, and keep writes behind approval.

A local hook stops the most common slip, where the agent pushes its work straight to main:

#!/bin/sh
# .git/hooks/pre-push
protected="refs/heads/main"
while read local_ref local_sha remote_ref remote_sha; do
  if [ "$remote_ref" = "$protected" ]; then
    echo "Direct pushes to main are blocked. Open a pull request." >&2
    exit 1
  fi
done

This hook only helps. Anything that can run git push --no-verify walks straight past it. The real control lives on the server, and that’s the next section.

Gate the merge with branch protection the agent cannot bypass

Branch protection rules set what has to happen before anything reaches a branch. By default a rule already blocks force pushes and branch deletion. For a branch that deploys to production, turn on the settings below. In the repository, open Settings, then Branches, then Add classic branch protection rule, and enter main as the branch name pattern.

  1. Select Require a pull request before merging, then Require approvals.
  2. Select Dismiss stale pull request approvals when new commits are pushed. An approval then covers only the diff that was approved.
  3. Select Require approval of the most recent reviewable push. Someone other than the last pusher must approve. When the agent pushed last, a person has to sign off.
  4. Select Require review from Code Owners and add a CODEOWNERS file for the paths where agent changes hurt most.
  5. Select Require status checks to pass before merging and Require branches to be up to date before merging. Search for your checks and pick each one’s expected source app.
  6. Select Require deployments to succeed before merging and choose your staging environment.
  7. Select Do not allow bypassing the above settings. Without it, admins and roles with the bypass permission skip every rule.
  8. In organisation repositories, select Restrict who can push to matching branches.
  9. Click Create.

A CODEOWNERS file sends the risky paths to the people who own them:

# .github/CODEOWNERS
/migrations/         @acme/db-owners
/infra/              @acme/platform
/.github/workflows/  @acme/platform
/tests/              @acme/platform
/AGENTS.md           @acme/platform

If a path lists several owners, an approval from any one of them is enough. Once the rule is live, a direct push that skips a failing check is refused on the server:

remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Required status check "ci-build" is failing

Only one classic branch protection rule applies to a branch at a time. If several patterns overlap, it can be hard to tell which one is in force. Rulesets don’t have that limit, so consider converting once you have more than one rule.

Make required checks block the merge they are meant to block

A required check protects you only if a failure really shows up as a failure. With GitHub Actions, the common trap is job dependencies. When a job fails, the jobs that depend on it are skipped, and a skipped job doesn’t report a failure. A pull request that requires the dependent job may merge anyway. The fix is one gate job that runs always(), reads the results of the jobs it needs, and is the only check you mark as required:

name: ci
on:
  pull_request:
  merge_group:
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: npm ci
      - run: npm test
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: npm ci
      - run: npm audit --audit-level=high
  gate:
    if: always()
    needs: [test, audit]
    runs-on: ubuntu-latest
    steps:
      - name: Fail if any needed job failed or was cancelled
        if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
        run: exit 1

Three more details matter once an agent is pushing commits:

  • Pin the source of each required check. Anyone with write permission can set the state of any status check in the repository, and an agent’s token has write permission. Pick the expected GitHub App for each check. A status set by anyone else then fails with a message that the check was not set by the expected app.
  • Add the merge_group trigger if you use a merge queue. Without it, required checks never run for queued pull requests and the merge fails.
  • Keep job names unique across workflows. Two workflows with the same job name give ambiguous results for a required check.

A workflow skipped by a path filter, a branch filter or a commit message instruction such as skip-checks: true leaves its checks pending. A pull request that requires those checks stays blocked, which is what you want. It also means anything you care about must be a required check. An agent can quietly skip checks that are merely optional.

Check the change against production before it merges

Tests check the code against itself. Many production failures depend on what is live: how big a table is, what older callers still send, which secrets exist in which environment. Say an agent adds an index to the orders table. Every test passes, because the test database is empty. In production, a plain CREATE INDEX holds a write lock on the table for the whole build, while checkout keeps trying to write to it.

Add review steps, in CI or in your reviewers’ checklist, for the changes that tests can’t judge:

  • Migrations that must be applied by hand, or in a set order relative to the deploy.
  • CREATE INDEX without CONCURRENTLY, and ADD COLUMN ... NOT NULL with no default. Both lock the table while they run.
  • An endpoint or field removed while the deployed version or another service still reads it.
  • Code that reads an environment variable, secret or binding that nothing in the diff provisions.
  • New dependencies. Confirm the package exists, check who maintains it, pin it in the lockfile and run software composition analysis. Models suggest package names that don’t exist, and attackers can register those names.
  • A raised timeout or limit presented as a fix. It usually hides the cause.

Some of these are easy to catch with a script. This one fails CI when a migration builds an index without CONCURRENTLY:

#!/bin/sh
# scripts/check-migrations.sh
git diff --name-only origin/main...HEAD -- 'migrations/*.sql' | while read -r f; do
  if grep -Eiq 'create (unique )?index' "$f" && ! grep -Eiq 'index concurrently' "$f"; then
    echo "$f: CREATE INDEX without CONCURRENTLY blocks writes on the table" >&2
    exit 1
  fi
done

A grep won’t find the rest, because they need to know what runs in production and what depends on it. Those need a person who knows the system, or a review step that can query your live telemetry and topology.

Write the rules down where the agent reads them

Agents read an instruction file such as AGENTS.md at the start of a task. Put your production rules there, in plain words:

# AGENTS.md

## Production rules
- Never push to main. Open a pull request and stop.
- Never run commands against production. Use read-only tools.
- Build indexes with CREATE INDEX CONCURRENTLY. New NOT NULL columns need a default.
- Ship schema changes in a separate pull request before the code that uses them.
- Name every new dependency in the pull request description with its registry link.
- Do not edit .github/workflows, infra/ or tests/ unless the task asks for it.
- Never disable, skip or delete a failing test. Report it in the pull request.
- Read secrets from the secrets manager. Never write a key into code or config.

## Scope
Change only what the task needs. List anything else you noticed in the pull request description.

A rules file guides the agent, and the model can still ignore it. Pair every rule with a check that enforces it: the pre-push hook and branch protection for pushes, CODEOWNERS for workflow and test edits, the migration script for indexes. Keep the file under version control and give it an owner in CODEOWNERS. A poisoned input that rewrites the rules should then show up in review.

A written specification does the same job for scope. Say what must change and what must stay as it is. Then compare the diff with it. A bug fix that also reorganises imports and deletes an unreliable test has gone out of scope.

Catch the change that gets through anyway

Some bad changes will pass every gate. Plan for that:

  1. Deploy to staging before production, and use the branch protection setting that requires the staging deployment to succeed. A preview environment per pull request catches integration problems even earlier.
  2. Record every deploy with its commit SHA. I built and led the Workers observability team at Cloudflare, and this is still the first thing I’d set up. When a graph moves, you want the change that landed just before it on the same timeline.
  3. Watch a few signals after each deploy: error rate, p95 latency, and the depth of any queue the change touches. Compare each one with its value before the deploy.
  4. Make rollback one step. Know the command before you need it:
git revert --no-edit -m 1 <merge-sha>
git push origin HEAD:revert-<merge-sha>
gh pr create --base main --fill

If your platform can redeploy the previous release in one action, use that first. Put the revert through review afterwards.

What goes wrong: the gaps agents slip through

These are the ways a setup that looks safe still lets an agent’s change through, with the fix for each.

  • The agent runs with an admin token. By default, branch protection doesn’t apply to admins or roles with the bypass permission. Fix: give the agent a non-admin identity and turn on Do not allow bypassing the above settings.
  • The agent marks its own check green. Any writer can set any status. Fix: pick the expected source app for every required check.
  • A failed job hides behind a skipped one. Dependent jobs are skipped and report nothing. Fix: require a single gate job that uses always() and needs.
  • The agent pushes after a person approved. The approval still counts unless you say otherwise. Fix: dismiss stale approvals and require approval of the most recent reviewable push.
  • The agent fixes CI by editing CI. It disables a test, loosens a lint rule or edits a workflow file. Fix: put workflows, tests and lint config under CODEOWNERS and require code owner review.
  • Local hooks are the only guard. --no-verify skips them. Fix: enforce the same rule on the server with branch protection.
  • The merge queue never runs the checks. The workflow lacks the merge_group trigger. Fix: add it next to pull_request.
  • Reviewers approve by reflex. Send every change to a person and people stop reading. Fix: keep human review for high-risk paths such as migrations, auth, infra and payments, and let deterministic checks clear the rest.
  • The agent follows instructions hidden in an issue. Fix: keep agents that read untrusted text on read-only credentials, and require approval for every write.
  • A new check can’t be selected as required. GitHub lists only checks that completed successfully in the repository in the past seven days. Fix: run the workflow once on a branch, then add it to the rule.

A checklist to finish this week

  • The agent has its own identity, with no admin rights and read-only cloud access.
  • There are no production secrets anywhere in the agent’s environment.
  • MCP servers are scoped, authenticated and logged, and their config goes through review.
  • main requires a pull request, approval of the latest push by someone else, code owner review and passing required checks, with bypassing turned off.
  • Required checks come from a pinned source app, and a single gate job decides pass or fail.
  • CODEOWNERS covers migrations, infra, workflows, tests and the agent rules file.
  • AGENTS.md states your production rules, and each rule has a check behind it.
  • Deploys go through staging, carry a commit SHA and can be rolled back in one step.

Checking agent pull requests against production with Polylane

Polylane reviews every pull request in a connected repository against the production resources it deploys to. It posts one pass or fail comment with the evidence, plus a check you can require in branch protection to block the merge. Your coding agent can query the same production context over its MCP server. Agent tools stay read-only until a session opts into writes, and every code fix arrives as a pull request that you merge.

See how Polylane's impact intelligence works.

Common questions.

Should I let an AI coding agent merge its own pull requests?

Not on branches that deploy to production. Turn on Require approval of the most recent reviewable push, so someone other than the last pusher has to approve. When the agent pushed last, that someone is a person. For paths that never ship to users, such as docs, auto merge after required checks pass can be fine.

Can an AI coding agent bypass GitHub branch protection?

Yes, if it holds an admin token or a role with the bypass branch protections permission, because the rules skip those actors by default. It can also set a status check to green, since any writer can set any status. Fix both: give it a non-admin identity, turn on Do not allow bypassing the above settings, and pin the expected source app for every required check.

Do protected branches work on private repositories?

Protected branches work in public repositories on GitHub Free and GitHub Free for organisations. For private repositories you need GitHub Pro, Team, Enterprise Cloud or Enterprise Server. Restricting who can push to a branch also needs an organisation-owned repository.

Why can't I add my new CI check as a required status check?

GitHub only lets you require checks that completed successfully in the repository during the past seven days. Push a branch that triggers the workflow, let it pass once, then pick the check in the branch protection rule. Give the job a name that no other workflow uses.

What should go in AGENTS.md to protect production?

The rules you would tell a new hire: never push to main, never run commands against production, build indexes concurrently, name every new dependency, don't touch workflows or tests unless asked, and never disable a failing test. Keep it short and specific. Back each rule with a server-side check, and put the file under CODEOWNERS so edits get reviewed.

How do I stop an agent adding a malicious or made-up package?

Treat every new dependency as a review item. Confirm the package exists, check who maintains it and its release history, pin it in the lockfile, and run software composition analysis in CI. For more control, resolve packages from a scoped internal registry or an allow-list of approved packages.

Do I still need staging if every pull request is reviewed?

Yes. Review and preview environments check one change at a time. Staging is where merged changes, migrations and shared libraries first run together. Branch protection can require a successful deployment to your staging environment before a pull request merges.

How do I protect against prompt injection in coding agents?

Assume any text from outside your team can carry instructions: issues, web pages, package READMEs and tool responses. Give agents that read that content read-only, least-privilege credentials. Require a person's approval for irreversible actions, and keep the agent's rules file under version control so tampering shows up in review.

Sources

  1. Balancing speed and safety: A control framework for AI coding agents (AWS Security Blog)
  2. About protected branches (GitHub Docs)
  3. Managing a branch protection rule (GitHub Docs)
  4. Status checks (GitHub Docs)
  5. Troubleshooting required status checks (GitHub Docs)
  6. Slopsquatting (Wikipedia)
  7. Polylane documentation
  8. Polylane full content, including How we prevent slop from hitting prod

About the author

Boris Tane

Founder of Polylane

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

More in this series

AI x Production: coding agents, MCP and autofix

  1. 1 How to Use Claude Code to Debug Production Issues
  2. 2 How to keep AI coding agents from breaking production

Related

Nobody should be on-call. Polylane watches your infra, finds what broke, and writes the fix.

Get started for free