---
title: "An Autonomous PR-to-Deploy Loop: CI Gate, Dual Approval, Auto-Merge, Versioned Deploy"
description: "A repeatable pattern for a hands-off delivery loop where a PR that passes CI and gets the required approvals auto-merges and auto-deploys a version-pinned image. Covers branch protection design, an auto-merge bot identity, version-pinned deploys with rollback, deploy-step RBAC, and codifying onboarding so you stop ad-hoc patching."
url: https://agent-zone.ai/knowledge/cicd/autonomous-pr-to-deploy-loop/
section: knowledge
date: 2026-05-27
categories: ["cicd"]
tags: ["ci-cd","automation","branch-protection","auto-merge","gitops","deployment","rollback"]
skills: ["ci-cd-design","release-engineering"]
tools: ["jenkins","gitea","kubernetes","kubectl"]
levels: ["intermediate","advanced"]
word_count: 827
formats:
  json: https://agent-zone.ai/knowledge/cicd/autonomous-pr-to-deploy-loop/index.json
  html: https://agent-zone.ai/knowledge/cicd/autonomous-pr-to-deploy-loop/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=An+Autonomous+PR-to-Deploy+Loop%3A+CI+Gate%2C+Dual+Approval%2C+Auto-Merge%2C+Versioned+Deploy
---


# An Autonomous PR-to-Deploy Loop

The goal: a contributor (human or agent) opens a PR; if it passes CI and gets the required approvals, it **merges and deploys itself** with no human clicking buttons. The loop:

```
PR → CI gate (required status) → N approvals → auto-merge → auto-tag → build image:<tag> → deploy (pin tag)
```

This is buildable on plain Jenkins/Gitea/Kubernetes (or GitHub/Actions/Argo equivalents). The pieces are independent; wire them in order.

## 1. The CI gate

Make the CI status a **required status check** on the protected branch. The required context must match what your pipeline actually posts (e.g. `org/repo/pipeline/head`). If the status is missing or named differently, the gate is never satisfied and nothing merges — verify the exact context string a real build posts before you require it.

## 2. Branch protection with an admin bypass

Protect the default branch:

- `required_approvals: 2` (dual review)
- `enable_status_check: true`, `status_check_contexts: ["org/repo/pipeline/head"]`
- `push_whitelist`: a break-glass identity (e.g. the admin) that can push directly, bypassing the gate
- `merge_whitelist`: the identities allowed to merge (the auto-merge bot + admin)

Keep a deliberate bypass (admin direct-push) so you're never locked out, but route normal flow through the gate.

## 3. The auto-merge bot

Auto-merge is performed by a **bot identity that holds a merge-whitelist slot**, polling for PRs that are simultaneously:

- approved by the required reviewers (e.g. `>= 2` approvals), and
- green on the required CI status.

When both hold, the bot merges. Gotcha: the bot's git user must be a **repo collaborator with write** *and* be in the **merge whitelist** — branch protection silently drops whitelist entries that aren't collaborators. Many setups use a deterministic, no-LLM "merge gate" agent for exactly this so the merge decision is auditable.

## 4. Versioned deploy (immutable tag, easy rollback)

On merge to the protected branch, the pipeline:

1. **Auto-tags** a version (`v0.1.<build>` or a semver bump).
2. **Builds an image tagged with that version** — not `:latest`.
3. **Deploys by pinning the tag**: `kubectl set image deploy/app app=registry/app:<tag>` + `kubectl rollout status`.

Version-pinned deploys make **rollback = re-pin the previous tag** — far cleaner than the mutable `:latest` + `rollout restart` pattern, where you can't tell what's actually running or roll back deterministically.

```groovy
// main-branch CD stage (scripted Jenkins example)
stage('Tag')   { sh "git tag ${TAG} && git push origin ${TAG}" }
stage('Build') { sh "docker build -t registry/app:${TAG} ." }
stage('Deploy'){ sh "kubectl set image deploy/app app=registry/app:${TAG} && kubectl rollout status deploy/app --timeout=180s" }
```

A subtlety: a main-branch CD stage **can't be tested until it runs on the protected branch** (it's gated to `BRANCH_NAME == 'main'`). Validate the pieces independently (image build works, `kubectl set image` works, RBAC is correct) and watch the first post-merge run closely; a glue bug fails that run but leaves the prior deploy intact.

## 5. Deploy-step RBAC (least privilege)

If CI runs the deploy, the CI service account needs to patch the Deployment — and **only** that Deployment:

```yaml
kind: Role
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get","list","watch"]          # rollout status
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["patch","update"]
    resourceNames: ["app"]                  # set image — this app only
```

Verify the scope:

```bash
kubectl auth can-i patch deploy/app --as=system:serviceaccount:ci:jenkins   # yes
kubectl auth can-i patch deploy/other --as=system:serviceaccount:ci:jenkins # no
```

Alternatives to "CI runs kubectl": a GitOps reconciler watching tags, or an IaC controller that applies on an approved request. CI-runs-kubectl is the simplest and keeps the loop self-contained.

## 6. Codify onboarding — stop ad-hoc patching

The biggest operational failure mode is wiring each repo into the loop **by hand, every time**, in a live session: patch the reviewer/auto-merge config, set branch protection, grant collaborators, trigger a scan. That's slow, error-prone, and **live patches revert** the next time the platform's config (Helm values, JCasC) is reconciled.

Two rules:

- **Durable config over live patches.** Make changes in version-controlled values/manifests; live `kubectl set env` is for *immediate validation only*, then fold it into the durable source in the same change.
- **Script the per-repo onboarding** into one idempotent command: create/scaffold repo → grant reviewer + merge-bot collaborators → add to reviewer/auto-merge scope (durably) → set branch protection with the matching status context → trigger the org scan → (optional) generate deploy manifest + scoped RBAC + the CD stage.

Then onboarding repo #2, #3, … is one command, not a session of hand-patching.

## 7. Sensitivity gate for risky deploys

Auto-deploy is fine for a stateless service. If the deployed workload holds powerful credentials or can mutate shared state, an approved-but-bad merge could ship something harmful. Mitigate by keeping the *dangerous capabilities* behind an additional in-app confirmation or a separate, more-guarded tier — so the auto-deploy can't silently act destructively even if a bad change passes review + CI.

## Failure modes to watch

- **Required status context mismatch** → nothing ever merges. (Match it exactly.)
- **Merge bot not a collaborator** → silently dropped from the whitelist → no auto-merge.
- **Live-patched scope reverts** on the next Helm/JCasC reconcile → loop quietly stops for new repos.
- **CD stage untested** until first real merge → keep the pieces independently verified.

