Split View: Issue에서 배포까지 — AI가 리뷰하고 CI/CD가 검증·배포하는 자동화 파이프라인 만들기 (2025)
Issue에서 배포까지 — AI가 리뷰하고 CI/CD가 검증·배포하는 자동화 파이프라인 만들기 (2025)
프롤로그 — 선(line)이 아니라 고리(loop)
지난 글에서는 AI 개발 자동화의 부품을 다뤘다 — 어떤 에이전트가 있고, GitHub과 어떻게 연동되고, 티켓을 어떻게 맡기는지.
이번 글은 그 부품들을 하나의 파이프라인으로 조립한다. 목표는 이것이다:
사람이 Issue를 하나 올린다. 자리를 비운다. 잠시 후 검증된 배포가 프로덕션에 올라가 있다. 사람이 개입한 건 단 하나 — 머지 버튼을 누른 결정뿐이다.
그리고 더 중요한 것: 이건 선이 아니라 고리다. 배포가 끝이 아니다. 모니터링이 이상을 감지하면 자동 롤백하고, 그 사건이 새 Issue가 되어 파이프라인의 입구로 다시 들어온다. 시스템이 스스로를 고친다.
이 글은 그 고리를 7개 스테이지로 쪼개고, 각 스테이지의 게이트를 설계하고, GitHub Actions로 전부 구현한다. 핵심 원칙은 하나다:
"작업은 자동화하고, 결정은 게이트한다 (Automate the work, gate the decisions)."
AI가 코드를 짜고 리뷰하고 배포하게 두되, 되돌릴 수 없는 결정(프로덕션 머지, 스키마 변경, 보안 로직)에는 반드시 사람 또는 강한 자동 검증을 세운다.
0장 · 전체 파이프라인 조감도
먼저 큰 그림. 7개 스테이지와 각 스테이지를 누가 소유하는지.
┌────────────────────────────────────────────────────────────────┐
│ │
│ [S1] Issue ──ai-ready 라벨──▶ AI 에이전트 ──▶ Draft PR │
│ ▲ │ │
│ │ ▼ │
│ │ [S2] AI 코드 리뷰 │
│ │ (생성≠리뷰) │
│ │ │ │
│ │ ▼ │
│ │ [S3] CI 검증 게이트 │
│ │ lint·type·test·build·E2E │
│ │ │ │
│ │ ┌── 실패 ──────┤ │
│ │ ▼ ▼ 통과 │
│ │ AI 자가 수정 [S4] Human Gate │
│ │ (로그 읽고 재푸시) 머지 결정 │
│ │ │ │
│ │ ▼ │
│ │ [S5] CD: Preview │
│ │ → Canary → Production │
│ │ │ │
│ │ ▼ │
│ │ [S6] 자동 검증 │
│ │ smoke·E2E·SLO 게이트 │
│ │ │ │
│ │ ┌── 실패 ──────┤ │
│ │ ▼ ▼ 통과 │
│ └──── 자동 Issue 생성 ◀─ [S7] 모니터링 & 피드백 │
│ (자동 롤백 + 사건화) 배포 후 관측, 롤백 트리거 │
│ │
└────────────────────────────────────────────────────────────────┘
| 스테이지 | 이름 | 소유 | 출력 |
|---|---|---|---|
| S1 | Issue → PR | AI 에이전트 | Draft PR |
| S2 | AI 코드 리뷰 | AI 리뷰어 (생성과 다른 모델) | 인라인 코멘트 + APPROVE/REQUEST_CHANGES |
| S3 | CI 검증 게이트 | CI 시스템 | 초록불/빨간불 |
| S4 | Human Gate | 사람 | 머지 결정 |
| S5 | CD 배포 | CD 시스템 | Preview → Canary → Production |
| S6 | 자동 검증 | 검증 시스템 | 승격 또는 롤백 |
| S7 | 모니터링 & 피드백 | 관측 시스템 | 정상 또는 자동 Issue |
이 글의 나머지는 각 스테이지를 하나씩 깊게 판다.
1장 · 파이프라인 설계 5원칙
구현 전에 원칙을 못 박는다. 이게 흔들리면 파이프라인이 위험해진다.
원칙 1 — 모든 스테이지는 fail-closed
스테이지가 실패하거나 판단이 불확실하면 멈춘다. 통과시키지 않는다. AI 리뷰가 헷갈리면 → REQUEST_CHANGES. CI가 flaky하면 → 빨간불. 검증이 모호하면 → 롤백. 의심스러우면 진행하지 않는다.
원칙 2 — 모든 스테이지는 되돌릴 수 있어야 한다
PR은 close, 머지는 revert, 배포는 rollback. 각 스테이지에 "취소 버튼"이 없으면 그 스테이지는 자동화하면 안 된다.
원칙 3 — 모든 스테이지는 관측 가능해야 한다
누가·언제·무엇을·왜 했는지 로그가 남는다. AI가 한 일은 특히. 디버깅 불가능한 자동화는 시한폭탄이다.
원칙 4 — 멱등성 (Idempotency)
같은 Issue가 두 번 트리거돼도 PR이 두 개 생기면 안 된다. 같은 커밋이 두 번 배포돼도 안전해야 한다. 재시도가 안전해야 자동화가 안전하다.
원칙 5 — 신뢰 사다리 (Trust Ladder)
처음부터 다 자동화하지 않는다. 저위험·고빈도 작업부터 자동화하고, 성공률 데이터가 쌓이면 자동화 범위를 넓힌다. 문서 수정 자동 머지 → 의존성 범프 자동 머지 → … 한 칸씩.
2장 · S1 — Issue → PR (생성 스테이지)
지난 글에서 다룬 부분이라 핵심만. ai-ready 라벨이 트리거, AI 에이전트가 브랜치를 만들고 구현하고 Draft PR을 연다.
이 스테이지의 진짜 목표는 "코드를 짜는 것"이 아니라 **"리뷰 가능한 PR을 낳는 것"**이다. 뒤의 모든 스테이지가 PR 품질에 의존한다.
PR이 "리뷰 가능하게 태어나는" 조건
- Draft 상태로 생성 — 사람이 "Ready for review"로 승격하기 전엔 머지 불가.
- Issue 링크 — PR 본문에
Closes #123. 머지 시 Issue 자동 close. - 작업 요약 — 에이전트가 "무엇을·왜" 했는지 본문에 남긴다. 리뷰어의 출발점.
- 일관된 PR 제목 —
[AI] fix: ...처럼. 뒤 스테이지가 제목으로 분기한다. ai-generated라벨 — 추적용.
멱등성 — 중복 PR 방지
# 같은 이슈에 PR이 이미 있으면 새로 안 만든다
- name: Check existing PR
id: check
run: |
EXISTING=$(gh pr list --search "in:title #${{ github.event.issue.number }}" --json number --jq 'length')
echo "exists=$EXISTING" >> "$GITHUB_OUTPUT"
- name: Run agent
if: steps.check.outputs.exists == '0'
uses: anthropics/claude-code-action@v1
# ...
3장 · S2 — AI 코드 리뷰 자동화 (핵심 스테이지)
여기가 이 파이프라인의 심장이다. 사람 리뷰 전에 AI 리뷰를 한 겹 끼운다.
왜 사람 리뷰 전에 AI 리뷰인가
- 노이즈 필터 — 명백한 실수(누락된 에러 처리, 타입 불일치, 컨벤션 위반)를 사람이 보기 전에 잡는다.
- 사람 리뷰 시간 절약 — 사람은 "이게 맞는 접근인가" 같은 판단에 집중. 잔손질은 AI가.
- 24/7 — PR이 새벽에 올라와도 즉시 리뷰.
철칙: 생성 ≠ 리뷰 (다른 모델, 다른 에이전트)
PR을 만든 에이전트가 자기 PR을 리뷰하게 두지 마라. 자기 코드의 사각지대는 자기가 못 본다. 가능하면 다른 모델로 리뷰한다. Claude가 짰으면 다른 도구가 리뷰하거나, 최소한 별도 세션·별도 프롬프트로.
생성 에이전트 (Claude/Copilot) ──▶ PR
│
리뷰 에이전트 (다른 모델/도구) ──▶ 리뷰 코멘트
│
사람 ──────────────────────────▶ 최종 결정
도구 지형도
| 도구 | 형태 | 특징 |
|---|---|---|
| CodeRabbit | GitHub App | PR마다 자동 리뷰, .coderabbit.yaml 설정, 요약+인라인 |
| Greptile | GitHub App | 코드베이스 전체 컨텍스트 기반 리뷰 |
| GitHub Copilot 리뷰 | 내장 | Copilot을 PR 리뷰어로 지정 가능 |
| Claude Code Action | Actions | @claude 또는 워크플로로 리뷰, 커스터마이즈 강함 |
AI 리뷰가 잘 잡는 것 / 못 잡는 것
| 잘 잡음 | 못 잡음 |
|---|---|
| 누락된 null·에러 처리 | "이게 올바른 아키텍처인가" |
| 컨벤션·네이밍 위반 | 비즈니스 요구사항 충족 여부 |
| 명백한 보안 패턴 (SQLi, 하드코딩 시크릿) | 도메인 특화 미묘한 버그 |
| 테스트 커버리지 공백 | 성능의 미묘한 트레이드오프 |
| 흔한 버그 패턴 | 팀의 암묵적 맥락 |
→ 그래서 사람 게이트(S4)가 필요하다. AI 리뷰는 사람을 대체하지 않고 사람의 부담을 줄인다.
구현 — Claude Code Action으로 리뷰
name: AI Review
on:
pull_request:
types: [opened, synchronize, ready_for_review]
jobs:
ai-review:
# draft가 아닐 때만 — 작업 중인 PR은 리뷰 안 함
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
이 PR을 리뷰하라. 다음에 집중:
- 정확성: 로직 버그, 엣지 케이스, 누락된 에러 처리
- 보안: 입력 검증, 시크릿, 권한
- 테스트: 변경에 대한 커버리지가 충분한가
- 컨벤션: CLAUDE.md를 따르는가
문제는 해당 라인에 인라인 코멘트로 남겨라.
마지막에 요약과 함께 APPROVE 또는 REQUEST_CHANGES 를 명시하라.
사소한 스타일은 nit: 으로 표시해 사람이 무시할 수 있게 하라.
AI 리뷰를 "필수 체크"로 만들기
리뷰가 코멘트만 남기면 무시당한다. 상태 체크(status check)로 만들어 브랜치 보호 규칙에 넣으면, REQUEST_CHANGES일 때 머지 자체가 막힌다. 리뷰 액션이 GitHub Checks API로 pass/fail을 보고하도록 구성한다.
4장 · S3 — CI 검증 게이트
AI가 짠 코드를 기계가 검증하는 단계. CI가 약하면 이 파이프라인 전체가 약하다.
계층화된 체크
빠름 ──────────────────────────────────▶ 느림
lint → typecheck → unit test → build → integration → E2E
│ │ │ │ │ │
초 단위 초 단위 초~분 분 분 분~십분
빠른 체크를 앞에 둬서 빨리 실패하게 한다. lint에서 깨질 걸 E2E까지 가서 알 필요 없다.
CI를 "AI가 읽을 수 있게" 만들기
이게 핵심인데 자주 놓친다. CI 실패 메시지가 친절하면 AI가 스스로 고친다.
Error: test failed(나쁨) →Error: expected status 200, got 401 at auth.test.ts:42(좋음)- 실패한 명령, 파일·라인, 기대값·실제값을 출력하라.
- AI 에이전트는 이 로그를 읽고 다음 커밋에서 자가 수정한다.
자가 수정 루프
PR 푸시 ──▶ CI 실행 ──▶ 실패
│
▼
에이전트가 CI 로그를 읽음
│
▼
원인 파악 → 수정 커밋 푸시
│
▼
CI 재실행 ──▶ 통과
이 루프에 상한을 둔다 — 3회 시도 후에도 빨간불이면 사람을 호출한다. 무한 자가 수정은 비용 폭탄이다.
# CI 실패 시 에이전트에 로그를 주고 수정 시도 — 단, 라벨로 횟수 제한
- name: Self-heal on CI failure
if: failure() && !contains(github.event.pull_request.labels.*.name, 'ai-heal-exhausted')
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
CI가 실패했다. 아래 로그를 읽고 원인을 고쳐 커밋하라.
추측하지 말고 로그가 가리키는 것만 고쳐라.
브랜치 보호 — 게이트를 강제
Settings → Branches → main 보호 규칙:
✅ Require status checks: ci/lint, ci/test, ci/build, ai-review
✅ Require branches to be up to date before merging
✅ Require a pull request before merging
✅ Require approvals: 1+ (S4 사람 게이트)
✅ Dismiss stale approvals when new commits pushed
✅ Do not allow bypassing the above
5장 · S4 — Human Gate (사람이 지키는 관문)
자동화의 핵심은 역설적으로 "사람이 어디를 지키는가"를 명확히 하는 것이다.
사람이 반드시 결정해야 하는 것
- 머지 결정 그 자체 — "이걸 main에 넣는다"는 책임은 사람이 진다.
- 아키텍처 방향 — "이 접근이 맞는가"는 AI가 판단 못 한다.
- 비즈니스 요구사항 충족 — Issue의 의도를 진짜 만족했는가.
- 보안·데이터 민감 변경 — 실수 비용이 큰 영역.
사람 리뷰를 빠르게 만드는 것들
S4가 병목이 되지 않으려면, 사람이 PR을 받았을 때 이미:
- ✅ AI 리뷰가 끝나 있다 (잔손질 다 처리됨)
- ✅ CI가 초록불이다 (기계 검증 통과)
- ✅ PR이 작다 (리뷰 가능한 크기)
- ✅ 설명이 좋다 (무엇을·왜)
- ✅ Preview 배포 URL이 붙어 있다 (실제로 눌러볼 수 있음)
그러면 사람 리뷰는 "판단"만 하면 된다. 5분이면 끝난다.
자동 머지 — 언제 OK인가
GitHub의 auto-merge는 "모든 필수 체크 통과 + 필수 승인 완료 시 자동 머지"다. 위험하지만, 특정 변경 유형에는 안전하다.
| 자동 머지 OK | 자동 머지 금지 |
|---|---|
| 문서·주석 수정 | 애플리케이션 로직 |
| 의존성 패치 범프 (CI 통과 시) | 스키마 마이그레이션 |
| 린트·포맷 자동 수정 | 보안·인증 로직 |
| 생성된 타입·SDK 갱신 | 인프라·IaC |
# 의존성 PR이고 CI 통과 + AI 리뷰 APPROVE면 자동 머지 활성화
- name: Enable auto-merge for safe deps
if: |
contains(github.event.pull_request.labels.*.name, 'dependencies') &&
github.event.pull_request.user.login == 'dependabot[bot]'
run: gh pr merge --auto --squash "${{ github.event.pull_request.number }}"
에스컬레이션 경로
AI(생성이든 리뷰든)가 불확실하면 사람을 태그한다. "이 변경은 인증 로직을 건드립니다 — 보안 담당자 리뷰 필요" 같은 코멘트를 자동으로 남기고 needs-human 라벨을 붙인다.
6장 · S5 — CD: Preview → Canary → Production
머지됐다. 이제 배포. 핵심은 한 번에 다 배포하지 않는 것이다.
Preview 배포 — PR마다
PR이 열리면 격리된 실제 환경에 배포해 고유 URL을 준다. Vercel·Netlify·Cloudflare는 기본 제공, 자체 인프라는 PR별 네임스페이스로 구현.
→ S4의 사람 리뷰어가 코드가 아니라 동작하는 결과물을 본다. AI 리뷰어도 Preview URL에 E2E를 돌릴 수 있다.
머지 후 — 단계적 전달 (Progressive Delivery)
main 머지
│
▼
Canary 배포 (트래픽 5%)
│
▼ [S6 자동 검증]
smoke test + SLO 관측 (5~15분)
│
├── 실패 ──▶ 자동 롤백 (Canary 제거)
│
▼ 통과
Production 승격 (트래픽 100%)
- Canary — 새 버전에 트래픽 일부(5~10%)만. 문제가 나도 영향 범위가 작다.
- Feature Flag — 코드는 배포하되 기능은 꺼둔 채. 켜는 건 별도 결정.
- 환경 승격 — dev → staging → prod. 각 단계가 게이트.
GitHub Environments로 게이트 구현
name: Deploy
on:
pull_request:
types: [closed]
branches: [main]
jobs:
deploy-canary:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment: canary # 보호 규칙: 없음 (자동)
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh canary
promote-production:
needs: [deploy-canary, verify-canary]
runs-on: ubuntu-latest
environment: production # 보호 규칙: required reviewers = 사람 게이트
steps:
- run: ./scripts/deploy.sh production
environment: production에 required reviewers를 걸면, 프로덕션 승격에 사람 승인이 강제된다 — 워크플로 안에 박힌 또 하나의 Human Gate.
7장 · S6 — 자동 검증 (Auto-Verification)
배포했다고 끝이 아니다. 배포된 것이 실제로 동작하는지 기계가 확인한다.
배포 후 검증 계층
| 검증 | 무엇을 | 언제 |
|---|---|---|
| Smoke test | 핵심 경로 몇 개 (로그인, 헬스체크) | 배포 직후 즉시 |
| E2E | 주요 사용자 시나리오 | Canary 단계 |
| Synthetic monitoring | 외부에서 주기적 핑 (Checkly, Datadog) | 지속 |
| SLO 관측 | 에러율·레이턴시·가용성 | Canary 윈도우 동안 |
| Visual regression | UI 픽셀 차이 | Preview/Canary |
검증 게이트 — Canary를 승격할까 롤백할까
verify-canary:
needs: deploy-canary
runs-on: ubuntu-latest
outputs:
verdict: ${{ steps.gate.outputs.verdict }}
steps:
- name: Smoke test
run: ./scripts/smoke-test.sh https://canary.example.com
- name: Observe SLO for 10 minutes
id: gate
run: |
sleep 600
ERROR_RATE=$(curl -s "$METRICS_API/error-rate?env=canary&window=10m")
P99=$(curl -s "$METRICS_API/latency-p99?env=canary&window=10m")
# 에러율 1% 초과 또는 p99 500ms 초과면 fail
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
echo "verdict=rollback" >> "$GITHUB_OUTPUT"; exit 1
fi
echo "verdict=promote" >> "$GITHUB_OUTPUT"
rollback-canary:
needs: verify-canary
if: failure()
runs-on: ubuntu-latest
steps:
- run: ./scripts/rollback.sh canary
검증이 실패하면 → Canary 자동 제거. 프로덕션 트래픽은 애초에 5%만 영향받았고, 그것도 즉시 회수된다.
8장 · S7 — 모니터링 & 피드백 루프 (고리를 닫는다)
여기서 파이프라인이 선에서 고리가 된다. 배포 후에도 시스템은 계속 지켜본다.
자동 롤백 트리거
프로덕션 승격 후에도 관측은 계속된다. SLO를 깨면 자동 롤백.
- 에러 예산 소진 (error budget burn rate) — 빠르게 타들어가면 롤백.
- 레이턴시 급등 — p99가 임계 초과.
- 핵심 비즈니스 메트릭 하락 — 결제 성공률, 가입 전환율 등.
자가 치유 루프 — 사건이 다시 Issue가 된다
이게 고리의 핵심이다. 자동 롤백이 일어나면, 그 사건을 새 Issue로 만들어 파이프라인 입구(S1)로 돌려보낸다.
name: Monitor & Auto-Rollback
on:
schedule:
- cron: '*/5 * * * *' # 5분마다 SLO 점검
workflow_run:
workflows: ["Deploy"]
types: [completed]
jobs:
slo-check:
runs-on: ubuntu-latest
steps:
- name: Check production SLO
id: slo
run: |
BURN=$(curl -s "$METRICS_API/error-budget-burn?env=prod&window=1h")
echo "burn=$BURN" >> "$GITHUB_OUTPUT"
- name: Rollback + file issue if breaching
if: ${{ steps.slo.outputs.burn > 2.0 }}
run: |
./scripts/rollback.sh production
gh issue create \
--title "Auto-rollback: error budget burn rate ${{ steps.slo.outputs.burn }}" \
--label "ai-ready,incident,priority-high" \
--body "프로덕션 SLO 위반으로 자동 롤백했다.
마지막 배포: ${{ github.sha }}
burn rate: ${{ steps.slo.outputs.burn }}
에이전트는 롤백된 커밋의 diff를 분석하고 원인을 진단해
수정 PR을 제출하라."
이 Issue에 ai-ready 라벨이 붙어 있으므로 → S1이 다시 트리거된다. AI가 롤백된 커밋을 분석하고, 수정 PR을 만들고, AI 리뷰 → CI → 사람 게이트 → 재배포. 고리가 닫혔다.
닫힌 고리의 의미
Issue ──▶ PR ──▶ 리뷰 ──▶ CI ──▶ 머지 ──▶ 배포 ──▶ 검증 ──▶ 모니터
▲ │
└──────────────── 사건이 새 Issue로 ◀──────────────────────────┘
시스템이 자기가 낸 문제를 스스로 입력으로 받아 고친다. 사람은 여전히 머지 게이트를 지키지만, 탐지·진단·수정 착수·재배포의 반복 노동에서 해방된다.
9장 · 전체 구현 — GitHub Actions로 엮기
지금까지의 스테이지를 실제 파일로. 5개 워크플로가 이벤트로 연결된다.
워크플로 지도
| 파일 | 트리거 | 역할 |
|---|---|---|
ai-resolve.yml | issues: labeled | S1: Issue → PR |
ai-review.yml | pull_request: opened/synchronize | S2: AI 리뷰 |
ci.yml | pull_request: opened/synchronize | S3: 검증 게이트 |
deploy.yml | pull_request: closed (merged) | S5: Canary → Production |
monitor.yml | schedule + workflow_run | S6/S7: 검증·롤백·피드백 |
이벤트로 어떻게 연결되나
GitHub Actions는 중앙 오케스트레이터가 없다. 이벤트가 곧 연결선이다.
issue.labeled('ai-ready') ──▶ ai-resolve.yml ──▶ (PR 생성)
│
pull_request.opened ◀──────────────────────────────────┘
├──▶ ai-review.yml (AI 리뷰 → 체크 보고)
└──▶ ci.yml (검증 → 체크 보고)
│
[브랜치 보호: 모든 체크 + 사람 승인 대기] │
▼
pull_request.closed(merged) ──▶ deploy.yml ──▶ (Canary → 검증 → Production)
│
workflow_run('Deploy' completed) ──▶ monitor.yml │
schedule('*/5') ──▶ monitor.yml ◀─────┘
└──▶ SLO 위반 시: rollback + issue.create('ai-ready')
│
└──▶ (다시 ai-resolve.yml — 고리 완성)
ai-resolve.yml — S1
name: AI Resolve
on:
issues:
types: [labeled]
jobs:
resolve:
if: github.event.label.name == 'ai-ready'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Skip if PR already exists
id: dedup
run: |
N=$(gh pr list --search "#${{ github.event.issue.number }} in:body" --json number --jq 'length')
echo "exists=$N" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run agent
if: steps.dedup.outputs.exists == '0'
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Issue #${{ github.event.issue.number }}: ${{ github.event.issue.title }}
${{ github.event.issue.body }}
이 이슈를 구현하라. 브랜치를 만들고, 변경하고, 테스트를 통과시키고,
"Closes #${{ github.event.issue.number }}" 를 포함한 Draft PR을 열어라.
CLAUDE.md 컨벤션을 따르고, PR 본문에 무엇을·왜 했는지 요약하라.
ci.yml — S3
name: CI
on:
pull_request:
types: [opened, synchronize, ready_for_review]
concurrency:
group: ci-${{ github.event.pull_request.number }}
cancel-in-progress: true # 새 푸시 시 이전 실행 취소 — 비용 절감
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint # 빠른 것 먼저
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build
deploy.yml — S5 (6장 참조, 핵심만)
name: Deploy
on:
pull_request:
types: [closed]
branches: [main]
jobs:
canary:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment: canary
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh canary
verify:
needs: canary
runs-on: ubuntu-latest
steps:
- run: ./scripts/smoke-test.sh https://canary.example.com
- run: ./scripts/observe-slo.sh canary 600 # 10분 관측
production:
needs: verify
runs-on: ubuntu-latest
environment: production # required reviewers = Human Gate
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh production
rollback:
needs: [canary, verify]
if: failure()
runs-on: ubuntu-latest
steps:
- run: ./scripts/rollback.sh canary
리포 설정 체크리스트
- Secrets:
ANTHROPIC_API_KEY, 배포 자격증명. - Environments:
canary(자동),production(required reviewers 지정). - Branch protection (main): 필수 체크에
ci/verify,ai-review포함. 필수 승인 1+. - Labels:
ai-ready,ai-generated,needs-human,incident,dependencies.
10장 · 결정 게이트 매트릭스 — 무엇을 자동화하고 무엇을 사람이
파이프라인의 진짜 설계는 "어디까지 자동화하는가"다. 변경 유형별로 게이트를 다르게 건다.
| 변경 유형 | AI 생성 | AI 리뷰 | CI | 사람 게이트 | 자동 머지 | 배포 |
|---|---|---|---|---|---|---|
| 문서·주석 | ✅ | ✅ | ✅ | 선택 | ✅ 가능 | 자동 |
| 의존성 패치 범프 | ✅ | ✅ | ✅ 필수 | 선택 | ✅ 조건부 | Canary 자동 |
| 버그 픽스 (좁은 범위) | ✅ | ✅ | ✅ | ✅ 1명 | ❌ | Canary→자동 승격 |
| 기능 추가 | ✅ | ✅ | ✅ | ✅ 1명+ | ❌ | Canary→사람 승격 |
| 리팩터링 | ✅ | ✅ | ✅ + 커버리지 | ✅ 1명 | ❌ | Canary→자동 승격 |
| DB 마이그레이션 | ⚠️ AI 보조 | ✅ | ✅ | ✅ 필수 | ❌ | 사람 트리거 |
| 보안·인증 로직 | ⚠️ AI 보조 | ✅ | ✅ | ✅ 2명 | ❌ | 사람 트리거 |
| 인프라·IaC | ⚠️ AI 보조 | ✅ | ✅ plan diff | ✅ 필수 | ❌ | 사람 트리거 |
범례: ✅ 자동 / ⚠️ AI는 보조만, 사람이 주도 / ❌ 안 함
신뢰 사다리를 오르는 법
처음엔 모든 칸을 보수적으로 시작한다 — 자동 머지 다 끄고, 배포는 다 사람 트리거. 그리고 데이터로 한 칸씩 올린다:
- 문서 PR 자동 머지를 한 달 켜본다 → 사고 0건 → 유지.
- 의존성 범프 자동 머지를 켠다 → CI 통과율·롤백률 관찰 → 안정적이면 유지.
- 버그 픽스의 Canary 자동 승격을 켠다 → escape rate(놓친 버그) 관찰.
- …
성공률 데이터 없이 자동화 범위를 넓히지 마라. 신뢰는 적립되는 것이지 선언하는 게 아니다.
11장 · 안전장치 & 실패 모드
자동화가 빠를수록 사고도 빠르다. 각 실패 모드에 방어를 건다.
"AI가 AI를 승인" 문제
가장 위험한 안티패턴. 생성 에이전트가 자기 PR을 리뷰·승인하면 검증이 0이다. 방어:
- 생성과 리뷰는 다른 도구/모델.
- AI 리뷰의 APPROVE는 사람 승인을 대체하지 못한다 — 브랜치 보호의 "필수 승인"은 반드시 사람 계정.
- 봇 계정의 승인은 필수 승인 수에서 제외 설정.
파이프라인을 통한 Prompt Injection
Issue 본문, PR 코멘트, 코드, CI 로그 — 전부 에이전트의 입력이다. 공격자가 거기 명령을 심을 수 있다.
- 외부 사용자의 Issue/PR은 자동 트리거 금지 — 멤버가 단
ai-ready라벨만 트리거. - 에이전트 토큰 최소 권한 — 프로덕션·시크릿 접근 불가.
- 워크플로 파일 변경 감지 — PR이
.github/workflows/를 건드리면 무조건 사람 필수 리뷰.
비용 폭주
- 스텝 상한, 자가 수정 횟수 상한(3회).
concurrency+cancel-in-progress로 중복 실행 제거.- 동시 실행 에이전트 수 제한.
- 일/주 단위 비용 알람.
모든 스테이지는 fail-closed + 롤백
| 스테이지 | 실패 시 | 롤백 수단 |
|---|---|---|
| S1 생성 | PR 안 만듦, 이슈에 코멘트 | — |
| S2 리뷰 | REQUEST_CHANGES, 머지 차단 | — |
| S3 CI | 빨간불, 머지 차단 | — |
| S4 사람 | 머지 안 함 | — |
| S5 배포 | Canary 단계에서 중단 | rollback.sh canary |
| S6 검증 | 승격 안 함 | Canary 제거 |
| S7 모니터 | 자동 롤백 + 이슈화 | rollback.sh production |
감사 — 누가·언제·무엇을
모든 스테이지의 행위가 로그로 남아야 한다. AI가 한 일은 커밋 트레일러(Co-Authored-By:), PR 라벨(ai-generated), 워크플로 실행 기록으로 추적 가능해야 한다. "왜 이게 배포됐지"에 답할 수 없으면 그 자동화는 끄는 게 낫다.
12장 · 운영 — 메트릭과 점진적 신뢰
파이프라인을 켰다고 끝이 아니다. 측정하고 튜닝한다.
추적할 메트릭
| 메트릭 | 의미 | 건강한 방향 |
|---|---|---|
| Lead time (Issue→배포) | 한 바퀴 도는 시간 | ↓ |
| % auto-merged | 사람 없이 머지된 비율 | ↑ (단, escape rate 보면서) |
| AI 리뷰 정밀도 | AI 리뷰 지적 중 실제 유효 비율 | ↑ |
| Escape rate | 파이프라인을 통과한 버그 비율 | ↓ (가장 중요) |
| Rollback rate | 배포 중 롤백된 비율 | 낮고 안정적 |
| 자가 수정 성공률 | CI 실패 후 AI가 고친 비율 | ↑ |
| 사람 리뷰 대기 시간 | S4 큐 대기 | ↓ |
DORA 메트릭을 이 파이프라인에
전통적 DORA 4지표가 그대로 적용된다 — Deployment Frequency, Lead Time, Change Failure Rate, MTTR. AI 파이프라인의 목표는 Throughput을 올리되 Stability를 깨지 않는 것이다. Change Failure Rate가 오르면 자동화 범위를 줄여라.
병목은 거의 항상 S4
에이전트 10개가 PR 10개를 5분에 만들어도, 사람 리뷰가 하루 5개면 처리량은 하루 5개다. 생성 속도가 아니라 리뷰 용량이 진짜 상한이다. 그래서 튜닝의 초점은:
- AI 리뷰(S2)를 강화해 사람 리뷰 부담을 줄인다.
- PR을 더 작게 쪼개 리뷰를 빠르게 한다.
- 저위험 변경의 자동 머지를 (데이터를 보며) 늘려 S4를 우회시킨다.
점진적 신뢰 — 데이터로 사다리를 오른다
매주 메트릭을 본다. Escape rate가 낮고 안정적이면 → 신뢰 사다리(10장)를 한 칸 올린다. Escape rate가 오르거나 Rollback rate가 튀면 → 한 칸 내린다. 자동화 범위는 고정값이 아니라 데이터로 조절하는 다이얼이다.
에필로그 — 파이프라인이 곧 팀의 형태
이 글의 파이프라인을 다 구현하면 팀의 일하는 방식이 바뀐다.
- 개발자는 코드를 적게 타이핑하고 Issue를 잘 쓰고 PR을 잘 리뷰한다.
- 잔손질 리뷰는 AI가, 판단 리뷰는 사람이.
- 탐지·진단·수정 착수·재배포의 반복 노동은 시스템이.
- 사람은 되돌릴 수 없는 결정의 게이트를 지킨다.
핵심 통찰 세 가지로 정리한다.
-
선이 아니라 고리다. 배포가 끝이 아니다. 모니터링이 사건을 잡아 다시 Issue로 만들면, 시스템이 자기 문제를 입력으로 받아 고친다.
-
"작업은 자동화, 결정은 게이트." AI가 코드를 짜고 리뷰하고 배포하게 두되, 머지·스키마·보안 같은 비가역 결정에는 사람 또는 강한 자동 검증을 세운다. fail-closed가 기본값이다.
-
신뢰는 적립된다. 처음부터 다 자동화하지 말고, 저위험 작업부터, 메트릭(특히 escape rate)을 보며 한 칸씩 올린다. 자동화 범위는 데이터로 조절하는 다이얼이다.
역설적이게도 이 모든 자동화의 최종 목적은 사람을 일에서 빼는 것이 아니라, 사람을 가장 중요한 곳 — 판단과 결정 — 에 집중시키는 것이다. 파이프라인이 반복 노동을 흡수하면, 팀의 사고는 "어떻게 만들까"에서 "무엇을·왜 만들까"로 올라간다.
14개 항목 체크리스트
- 7개 스테이지를 다 식별하고 각 소유자를 정했는가?
- 모든 스테이지가 fail-closed인가?
- 모든 스테이지에 롤백/취소 수단이 있는가?
- 생성 에이전트와 리뷰 에이전트가 분리됐는가 (다른 모델)?
- AI 리뷰가 필수 상태 체크로 걸려 있는가?
- CI 실패 메시지가 AI가 읽을 만큼 친절한가?
- 자가 수정 루프에 횟수 상한이 있는가?
- 브랜치 보호에 필수 체크 + 사람 승인이 강제되는가?
- 사람 승인은 봇이 아닌 사람 계정만 카운트되는가?
- Preview·Canary·Production이 단계적으로 분리됐는가?
production환경에 required reviewers가 걸려 있는가?- 자동 검증(smoke·SLO)이 Canary 승격을 게이트하는가?
- SLO 위반 시 자동 롤백 + 자동 Issue 생성이 되는가 (고리)?
- Escape rate를 추적하고 그 데이터로 자동화 범위를 조절하는가?
안티패턴 10가지
- 생성 에이전트가 자기 PR을 리뷰·승인.
- AI 리뷰 APPROVE를 사람 승인으로 카운트.
- 애플리케이션 로직을 자동 머지.
- 머지 즉시 100% 프로덕션 배포 (Canary 없음).
- 배포 후 검증 없음 (smoke·SLO 게이트 부재).
- 자동 롤백은 있는데 사건이 Issue로 안 돌아옴 (고리가 안 닫힘).
- CI 실패 메시지가 불친절해 AI 자가 수정 불가.
- 자가 수정 루프에 상한 없음 → 비용 폭탄.
- 외부 사용자 Issue가 파이프라인을 자동 트리거.
- Escape rate 측정 없이 자동화 범위만 넓힘.
다음 글 예고
다음 글 후보: AI 코드 리뷰어 직접 만들기 — 팀 컨벤션을 학습하는 커스텀 리뷰 봇, Progressive Delivery 심층 — Argo Rollouts·Flagger로 SLO 기반 자동 승격, 에이전트 오케스트레이션 — LangGraph로 멀티 스테이지 파이프라인을 상태 머신으로.
"최고의 파이프라인은 사람을 대체하지 않는다. 사람이 결정만 하도록, 나머지 모든 반복을 흡수한다."
— Issue에서 배포까지, 자동화 파이프라인 만들기, 끝.
From Issue to Deploy — Building an Automation Pipeline Where AI Reviews and CI/CD Verifies and Ships (2025)
Prologue — a loop, not a line
In the previous post we covered the parts of AI development automation — which agents exist, how they integrate with GitHub, and how you hand them tickets.
This post assembles those parts into a single pipeline. The goal is this:
A human files one Issue. They step away. A short while later, a verified deployment is live in production. The only thing the human did was press the merge button — the decision.
And more importantly: this is a loop, not a line. Deployment is not the end. When monitoring detects an anomaly, it rolls back automatically, and that incident becomes a new Issue that re-enters the pipeline at its entrance. The system fixes itself.
This post splits that loop into 7 stages, designs the gate for each stage, and implements all of it with GitHub Actions. The core principle is a single one:
"Automate the work, gate the decisions."
Let AI write, review, and deploy code — but for irreversible decisions (production merge, schema changes, security logic), always put a human or a strong automated check in the way.
Chapter 0 · Bird's-eye view of the whole pipeline
The big picture first. The 7 stages and who owns each one.
┌────────────────────────────────────────────────────────────────┐
│ │
│ [S1] Issue ──ai-ready label──▶ AI agent ──▶ Draft PR │
│ ▲ │ │
│ │ ▼ │
│ │ [S2] AI code review │
│ │ (generate≠review) │
│ │ │ │
│ │ ▼ │
│ │ [S3] CI verification │
│ │ lint·type·test·build·E2E │
│ │ │ │
│ │ ┌── fail ──────┤ │
│ │ ▼ ▼ pass │
│ │ AI self-heal [S4] Human Gate │
│ │ (read logs, repush) merge decision │
│ │ │ │
│ │ ▼ │
│ │ [S5] CD: Preview │
│ │ → Canary → Production │
│ │ │ │
│ │ ▼ │
│ │ [S6] Auto-verification │
│ │ smoke·E2E·SLO gate │
│ │ │ │
│ │ ┌── fail ──────┤ │
│ │ ▼ ▼ pass │
│ └──── auto Issue creation ◀ [S7] Monitoring & feedback │
│ (auto rollback + incident) post-deploy observe, │
│ rollback trigger │
│ │
└────────────────────────────────────────────────────────────────┘
| Stage | Name | Owner | Output |
|---|---|---|---|
| S1 | Issue → PR | AI agent | Draft PR |
| S2 | AI code review | AI reviewer (model different from generator) | Inline comments + APPROVE/REQUEST_CHANGES |
| S3 | CI verification gate | CI system | Green light / red light |
| S4 | Human Gate | Human | Merge decision |
| S5 | CD deployment | CD system | Preview → Canary → Production |
| S6 | Auto-verification | Verification system | Promote or roll back |
| S7 | Monitoring & feedback | Observability system | Healthy or auto Issue |
The rest of this post digs deep into each stage, one at a time.
Chapter 1 · The 5 principles of pipeline design
Nail down the principles before implementing. If these wobble, the pipeline becomes dangerous.
Principle 1 — Every stage is fail-closed
When a stage fails or its judgment is uncertain, it stops. It does not let things through. AI review is confused → REQUEST_CHANGES. CI is flaky → red light. Verification is ambiguous → roll back. When in doubt, do not proceed.
Principle 2 — Every stage must be reversible
A PR can be closed, a merge can be reverted, a deployment can be rolled back. If a stage has no "cancel button," that stage must not be automated.
Principle 3 — Every stage must be observable
Logs record who did what, when, and why. Especially what AI did. Automation you cannot debug is a time bomb.
Principle 4 — Idempotency
If the same Issue is triggered twice, you must not get two PRs. If the same commit is deployed twice, it must be safe. Retries must be safe for automation to be safe.
Principle 5 — The trust ladder
Do not automate everything from the start. Automate low-risk, high-frequency work first, and as success-rate data accumulates, widen the automation scope. Auto-merge for doc fixes → auto-merge for dependency bumps → … one rung at a time.
Chapter 2 · S1 — Issue → PR (the generation stage)
This was covered in the previous post, so just the essentials. The ai-ready label is the trigger; the AI agent creates a branch, implements, and opens a Draft PR.
The real goal of this stage is not "writing code" but "producing a reviewable PR." Every stage downstream depends on PR quality.
The conditions for a PR to be "born reviewable"
- Created as a Draft — not mergeable until a human promotes it to "Ready for review."
- Issue link —
Closes #123in the PR body. The Issue closes automatically on merge. - Work summary — the agent records "what and why" in the body. The reviewer's starting point.
- Consistent PR title — like
[AI] fix: .... Downstream stages branch on the title. ai-generatedlabel — for tracking.
Idempotency — preventing duplicate PRs
# If a PR already exists for the same issue, don't create a new one
- name: Check existing PR
id: check
run: |
EXISTING=$(gh pr list --search "in:title #${{ github.event.issue.number }}" --json number --jq 'length')
echo "exists=$EXISTING" >> "$GITHUB_OUTPUT"
- name: Run agent
if: steps.check.outputs.exists == '0'
uses: anthropics/claude-code-action@v1
# ...
Chapter 3 · S2 — AI code review automation (the core stage)
This is the heart of this pipeline. Insert a layer of AI review before human review.
Why AI review before human review
- Noise filter — catch obvious mistakes (missing error handling, type mismatches, convention violations) before a human sees them.
- Saves human review time — humans focus on judgment like "is this the right approach." AI handles the touch-ups.
- 24/7 — even if a PR lands at 3 a.m., it gets reviewed immediately.
Iron rule: generate ≠ review (different model, different agent)
Do not let the agent that created the PR review its own PR. It cannot see the blind spots in its own code. Where possible, review with a different model. If Claude wrote it, have a different tool review it, or at minimum a separate session with a separate prompt.
Generation agent (Claude/Copilot) ──▶ PR
│
Review agent (different model/tool) ──▶ review comments
│
Human ─────────────────────────────▶ final decision
Tool landscape
| Tool | Form | Characteristics |
|---|---|---|
| CodeRabbit | GitHub App | Auto-reviews every PR, configured via .coderabbit.yaml, summary + inline |
| Greptile | GitHub App | Review based on full-codebase context |
| GitHub Copilot review | Built-in | Copilot can be designated as a PR reviewer |
| Claude Code Action | Actions | Review via @claude or a workflow, strongly customizable |
What AI review catches well / poorly
| Catches well | Catches poorly |
|---|---|
| Missing null / error handling | "Is this the right architecture" |
| Convention / naming violations | Whether business requirements are met |
| Obvious security patterns (SQLi, hardcoded secrets) | Subtle domain-specific bugs |
| Test coverage gaps | Subtle performance trade-offs |
| Common bug patterns | The team's implicit context |
→ This is exactly why the human gate (S4) is needed. AI review does not replace humans — it reduces the human's burden.
Implementation — review with Claude Code Action
name: AI Review
on:
pull_request:
types: [opened, synchronize, ready_for_review]
jobs:
ai-review:
# only when not a draft — don't review work-in-progress PRs
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Review this PR. Focus on:
- Correctness: logic bugs, edge cases, missing error handling
- Security: input validation, secrets, permissions
- Tests: is coverage sufficient for the change
- Conventions: does it follow CLAUDE.md
Leave issues as inline comments on the relevant lines.
At the end, state APPROVE or REQUEST_CHANGES with a summary.
Mark trivial style points with nit: so a human can ignore them.
Making AI review a "required check"
If review only leaves comments, it gets ignored. Make it a status check and put it in the branch protection rules — then a merge is blocked outright when it is REQUEST_CHANGES. Configure the review action to report pass/fail through the GitHub Checks API.
Chapter 4 · S3 — The CI verification gate
The stage where a machine verifies the code AI wrote. If CI is weak, this whole pipeline is weak.
Layered checks
fast ──────────────────────────────────▶ slow
lint → typecheck → unit test → build → integration → E2E
│ │ │ │ │ │
seconds seconds sec~min min min min~tens of min
Put the fast checks up front so it fails fast. There's no need to go all the way to E2E to learn something will break at lint.
Making CI "readable by AI"
This is the key part, and it's often missed. If CI failure messages are friendly, AI fixes them itself.
Error: test failed(bad) →Error: expected status 200, got 401 at auth.test.ts:42(good)- Print the failed command, the file and line, the expected and actual values.
- The AI agent reads this log and self-heals in the next commit.
The self-heal loop
PR push ──▶ CI runs ──▶ fail
│
▼
agent reads the CI log
│
▼
identify cause → push a fix commit
│
▼
CI re-runs ──▶ pass
Put a ceiling on this loop — if it's still red after 3 attempts, call a human. Infinite self-healing is a cost bomb.
# On CI failure, give the agent the logs and let it attempt a fix —
# but cap the number of attempts with a label
- name: Self-heal on CI failure
if: failure() && !contains(github.event.pull_request.labels.*.name, 'ai-heal-exhausted')
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
CI failed. Read the logs below, fix the cause, and commit.
Don't guess — only fix what the logs point to.
Branch protection — enforce the gate
Settings → Branches → main protection rules:
✅ Require status checks: ci/lint, ci/test, ci/build, ai-review
✅ Require branches to be up to date before merging
✅ Require a pull request before merging
✅ Require approvals: 1+ (S4 human gate)
✅ Dismiss stale approvals when new commits pushed
✅ Do not allow bypassing the above
Chapter 5 · S4 — The Human Gate (the gate a human guards)
The core of automation is, paradoxically, clearly defining "where the human stands guard."
What the human must decide
- The merge decision itself — the responsibility for "this goes into main" is the human's.
- Architecture direction — "is this approach right" is something AI cannot judge.
- Whether business requirements are met — did this genuinely satisfy the Issue's intent.
- Security and data-sensitive changes — areas where the cost of a mistake is high.
Things that make human review fast
For S4 not to become a bottleneck, when a human receives a PR, it should already have:
- ✅ AI review done (all touch-ups handled)
- ✅ CI green (machine verification passed)
- ✅ The PR is small (a reviewable size)
- ✅ A good description (what and why)
- ✅ A Preview deployment URL attached (you can actually click through it)
Then human review only needs to do "judgment." It's done in 5 minutes.
Auto-merge — when is it OK
GitHub's auto-merge means "merge automatically once all required checks pass and required approvals are complete." It's risky, but for certain change types it's safe.
| Auto-merge OK | Auto-merge forbidden |
|---|---|
| Doc / comment fixes | Application logic |
| Dependency patch bumps (when CI passes) | Schema migrations |
| Lint / format auto-fixes | Security / auth logic |
| Generated type / SDK updates | Infrastructure / IaC |
# If it's a dependency PR and CI passes + AI review APPROVE, enable auto-merge
- name: Enable auto-merge for safe deps
if: |
contains(github.event.pull_request.labels.*.name, 'dependencies') &&
github.event.pull_request.user.login == 'dependabot[bot]'
run: gh pr merge --auto --squash "${{ github.event.pull_request.number }}"
Escalation path
When AI (whether generation or review) is uncertain, it tags a human. It automatically leaves a comment like "this change touches auth logic — needs review by a security owner" and attaches the needs-human label.
Chapter 6 · S5 — CD: Preview → Canary → Production
It's merged. Now, deployment. The key is not deploying everything at once.
Preview deployment — per PR
When a PR opens, deploy to an isolated real environment and give it a unique URL. Vercel, Netlify, and Cloudflare provide this out of the box; for your own infrastructure, implement it with a per-PR namespace.
→ The human reviewer in S4 sees a working artifact, not code. The AI reviewer can also run E2E against the Preview URL.
After merge — progressive delivery
merge to main
│
▼
Canary deploy (5% of traffic)
│
▼ [S6 auto-verification]
smoke test + SLO observation (5~15 min)
│
├── fail ──▶ auto rollback (remove Canary)
│
▼ pass
Production promotion (100% of traffic)
- Canary — only a slice of traffic (5~10%) goes to the new version. Even if a problem hits, the blast radius is small.
- Feature Flag — deploy the code but keep the feature off. Turning it on is a separate decision.
- Environment promotion — dev → staging → prod. Each step is a gate.
Implementing the gate with GitHub Environments
name: Deploy
on:
pull_request:
types: [closed]
branches: [main]
jobs:
deploy-canary:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment: canary # protection rules: none (automatic)
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh canary
promote-production:
needs: [deploy-canary, verify-canary]
runs-on: ubuntu-latest
environment: production # protection rules: required reviewers = human gate
steps:
- run: ./scripts/deploy.sh production
Put required reviewers on environment: production and human approval is enforced for production promotion — another Human Gate baked right into the workflow.
Chapter 7 · S6 — Auto-verification
Deploying isn't the end. A machine confirms that what was deployed actually works.
Post-deploy verification layers
| Verification | What | When |
|---|---|---|
| Smoke test | A few core paths (login, health check) | Immediately after deploy |
| E2E | Major user scenarios | Canary stage |
| Synthetic monitoring | Periodic pings from outside (Checkly, Datadog) | Continuous |
| SLO observation | Error rate, latency, availability | During the Canary window |
| Visual regression | UI pixel diffs | Preview/Canary |
The verification gate — promote the Canary or roll back
verify-canary:
needs: deploy-canary
runs-on: ubuntu-latest
outputs:
verdict: ${{ steps.gate.outputs.verdict }}
steps:
- name: Smoke test
run: ./scripts/smoke-test.sh https://canary.example.com
- name: Observe SLO for 10 minutes
id: gate
run: |
sleep 600
ERROR_RATE=$(curl -s "$METRICS_API/error-rate?env=canary&window=10m")
P99=$(curl -s "$METRICS_API/latency-p99?env=canary&window=10m")
# fail if error rate exceeds 1% or p99 exceeds 500ms
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
echo "verdict=rollback" >> "$GITHUB_OUTPUT"; exit 1
fi
echo "verdict=promote" >> "$GITHUB_OUTPUT"
rollback-canary:
needs: verify-canary
if: failure()
runs-on: ubuntu-latest
steps:
- run: ./scripts/rollback.sh canary
If verification fails → the Canary is removed automatically. Production traffic was only ever 5% affected, and even that is reclaimed immediately.
Chapter 8 · S7 — Monitoring & the feedback loop (closing the loop)
This is where the pipeline goes from a line to a loop. Even after deployment, the system keeps watching.
Auto-rollback triggers
Observation continues even after production promotion. Break the SLO and it rolls back automatically.
- Error budget burn rate — if it burns down fast, roll back.
- Latency spike — p99 exceeds the threshold.
- Core business metric drop — payment success rate, signup conversion rate, etc.
The self-healing loop — an incident becomes an Issue again
This is the core of the loop. When an auto-rollback happens, the incident is turned into a new Issue and sent back to the pipeline's entrance (S1).
name: Monitor & Auto-Rollback
on:
schedule:
- cron: '*/5 * * * *' # check SLO every 5 minutes
workflow_run:
workflows: ["Deploy"]
types: [completed]
jobs:
slo-check:
runs-on: ubuntu-latest
steps:
- name: Check production SLO
id: slo
run: |
BURN=$(curl -s "$METRICS_API/error-budget-burn?env=prod&window=1h")
echo "burn=$BURN" >> "$GITHUB_OUTPUT"
- name: Rollback + file issue if breaching
if: ${{ steps.slo.outputs.burn > 2.0 }}
run: |
./scripts/rollback.sh production
gh issue create \
--title "Auto-rollback: error budget burn rate ${{ steps.slo.outputs.burn }}" \
--label "ai-ready,incident,priority-high" \
--body "Auto-rolled back due to a production SLO violation.
Last deploy: ${{ github.sha }}
burn rate: ${{ steps.slo.outputs.burn }}
The agent should analyze the diff of the rolled-back commit, diagnose the cause,
and submit a fix PR."
This Issue has the ai-ready label attached, so → S1 triggers again. AI analyzes the rolled-back commit, creates a fix PR, AI review → CI → human gate → redeploy. The loop is closed.
What a closed loop means
Issue ──▶ PR ──▶ review ──▶ CI ──▶ merge ──▶ deploy ──▶ verify ──▶ monitor
▲ │
└──────────── incident becomes a new Issue ◀───────────────────────┘
The system takes the problem it created as its own input and fixes it. The human still guards the merge gate, but is freed from the repetitive labor of detection, diagnosis, kicking off the fix, and redeploying.
Chapter 9 · The full implementation — wiring it together with GitHub Actions
The stages so far, as actual files. 5 workflows connected by events.
Workflow map
| File | Trigger | Role |
|---|---|---|
ai-resolve.yml | issues: labeled | S1: Issue → PR |
ai-review.yml | pull_request: opened/synchronize | S2: AI review |
ci.yml | pull_request: opened/synchronize | S3: verification gate |
deploy.yml | pull_request: closed (merged) | S5: Canary → Production |
monitor.yml | schedule + workflow_run | S6/S7: verify, rollback, feedback |
How they connect via events
GitHub Actions has no central orchestrator. Events are the connecting wires.
issue.labeled('ai-ready') ──▶ ai-resolve.yml ──▶ (PR created)
│
pull_request.opened ◀──────────────────────────────────┘
├──▶ ai-review.yml (AI review → report check)
└──▶ ci.yml (verification → report check)
│
[branch protection: all checks + human approval awaited]
▼
pull_request.closed(merged) ──▶ deploy.yml ──▶ (Canary → verify → Production)
│
workflow_run('Deploy' completed) ──▶ monitor.yml │
schedule('*/5') ──▶ monitor.yml ◀─────┘
└──▶ on SLO violation: rollback + issue.create('ai-ready')
│
└──▶ (back to ai-resolve.yml — loop complete)
ai-resolve.yml — S1
name: AI Resolve
on:
issues:
types: [labeled]
jobs:
resolve:
if: github.event.label.name == 'ai-ready'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Skip if PR already exists
id: dedup
run: |
N=$(gh pr list --search "#${{ github.event.issue.number }} in:body" --json number --jq 'length')
echo "exists=$N" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run agent
if: steps.dedup.outputs.exists == '0'
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Issue #${{ github.event.issue.number }}: ${{ github.event.issue.title }}
${{ github.event.issue.body }}
Implement this issue. Create a branch, make the changes, get the tests passing,
and open a Draft PR that includes "Closes #${{ github.event.issue.number }}".
Follow the CLAUDE.md conventions, and summarize what and why in the PR body.
ci.yml — S3
name: CI
on:
pull_request:
types: [opened, synchronize, ready_for_review]
concurrency:
group: ci-${{ github.event.pull_request.number }}
cancel-in-progress: true # cancel the previous run on a new push — saves cost
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint # fast things first
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build
deploy.yml — S5 (see Chapter 6, essentials only)
name: Deploy
on:
pull_request:
types: [closed]
branches: [main]
jobs:
canary:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
environment: canary
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh canary
verify:
needs: canary
runs-on: ubuntu-latest
steps:
- run: ./scripts/smoke-test.sh https://canary.example.com
- run: ./scripts/observe-slo.sh canary 600 # observe for 10 min
production:
needs: verify
runs-on: ubuntu-latest
environment: production # required reviewers = Human Gate
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy.sh production
rollback:
needs: [canary, verify]
if: failure()
runs-on: ubuntu-latest
steps:
- run: ./scripts/rollback.sh canary
Repo setup checklist
- Secrets:
ANTHROPIC_API_KEY, deployment credentials. - Environments:
canary(automatic),production(required reviewers designated). - Branch protection (main): include
ci/verifyandai-reviewin required checks. Required approvals 1+. - Labels:
ai-ready,ai-generated,needs-human,incident,dependencies.
Chapter 10 · The decision-gate matrix — what to automate and what the human handles
The real design of a pipeline is "how far do you automate." You set different gates per change type.
| Change type | AI generation | AI review | CI | Human gate | Auto-merge | Deploy |
|---|---|---|---|---|---|---|
| Docs / comments | ✅ | ✅ | ✅ | optional | ✅ possible | automatic |
| Dependency patch bump | ✅ | ✅ | ✅ required | optional | ✅ conditional | Canary automatic |
| Bug fix (narrow scope) | ✅ | ✅ | ✅ | ✅ 1 person | ❌ | Canary → auto-promote |
| Feature addition | ✅ | ✅ | ✅ | ✅ 1+ person | ❌ | Canary → human promote |
| Refactoring | ✅ | ✅ | ✅ + coverage | ✅ 1 person | ❌ | Canary → auto-promote |
| DB migration | ⚠️ AI assists | ✅ | ✅ | ✅ required | ❌ | human-triggered |
| Security / auth logic | ⚠️ AI assists | ✅ | ✅ | ✅ 2 people | ❌ | human-triggered |
| Infrastructure / IaC | ⚠️ AI assists | ✅ | ✅ plan diff | ✅ required | ❌ | human-triggered |
Legend: ✅ automatic / ⚠️ AI assists only, human leads / ❌ not done
How to climb the trust ladder
Start with every cell conservative — all auto-merge off, all deployments human-triggered. Then raise one cell at a time with data:
- Turn on auto-merge for doc PRs for a month → 0 incidents → keep it.
- Turn on auto-merge for dependency bumps → watch the CI pass rate and rollback rate → if stable, keep it.
- Turn on Canary auto-promotion for bug fixes → watch the escape rate (missed bugs).
- …
Do not widen the automation scope without success-rate data. Trust is accrued, not declared.
Chapter 11 · Safety mechanisms & failure modes
The faster the automation, the faster the accidents. Put a defense on each failure mode.
The "AI approves AI" problem
The most dangerous anti-pattern. If the generation agent reviews and approves its own PR, verification is zero. Defenses:
- Generation and review are different tools/models.
- An AI review's APPROVE cannot replace human approval — the "required approval" in branch protection must be a human account.
- Configure bot account approvals to be excluded from the required approval count.
Prompt injection through the pipeline
Issue bodies, PR comments, code, CI logs — all of it is input to the agent. An attacker can plant commands there.
- No auto-triggering from external users' Issues/PRs — only the
ai-readylabel applied by a member triggers. - Minimal-privilege agent tokens — no access to production or secrets.
- Detect workflow file changes — if a PR touches
.github/workflows/, it always requires a mandatory human review.
Cost runaway
- Step ceilings, a self-heal attempt ceiling (3 times).
- Eliminate duplicate runs with
concurrency+cancel-in-progress. - Limit the number of concurrently running agents.
- Daily/weekly cost alarms.
Every stage is fail-closed + rollback
| Stage | On failure | Rollback means |
|---|---|---|
| S1 generation | No PR created, comment on the issue | — |
| S2 review | REQUEST_CHANGES, merge blocked | — |
| S3 CI | Red light, merge blocked | — |
| S4 human | Not merged | — |
| S5 deploy | Halt at the Canary stage | rollback.sh canary |
| S6 verification | Not promoted | Remove the Canary |
| S7 monitor | Auto rollback + filed as an issue | rollback.sh production |
Auditing — who, when, what
Every stage's actions must be recorded as logs. What AI did must be traceable via the commit trailer (Co-Authored-By:), the PR label (ai-generated), and the workflow run history. If you can't answer "why did this get deployed," it's better to turn that automation off.
Chapter 12 · Operations — metrics and gradual trust
Turning the pipeline on isn't the end. You measure and tune.
Metrics to track
| Metric | Meaning | Healthy direction |
|---|---|---|
| Lead time (Issue→deploy) | Time for one lap | ↓ |
| % auto-merged | Share merged without a human | ↑ (but watch the escape rate) |
| AI review precision | Share of AI review flags that are actually valid | ↑ |
| Escape rate | Share of bugs that got through the pipeline | ↓ (most important) |
| Rollback rate | Share of deployments rolled back | low and stable |
| Self-heal success rate | Share of CI failures AI fixed | ↑ |
| Human review wait time | S4 queue wait | ↓ |
Applying DORA metrics to this pipeline
The traditional 4 DORA metrics apply directly — Deployment Frequency, Lead Time, Change Failure Rate, MTTR. The goal of an AI pipeline is to raise Throughput without breaking Stability. If the Change Failure Rate rises, shrink the automation scope.
The bottleneck is almost always S4
Even if 10 agents create 10 PRs in 5 minutes, if human review handles 5 a day, throughput is 5 a day. The real ceiling is review capacity, not generation speed. So the focus of tuning is:
- Strengthen AI review (S2) to reduce the human review burden.
- Split PRs smaller to make review faster.
- Increase auto-merge for low-risk changes (while watching the data) to route around S4.
Gradual trust — climb the ladder with data
Look at the metrics every week. If the escape rate is low and stable → climb one rung of the trust ladder (Chapter 10). If the escape rate rises or the rollback rate spikes → step down one rung. The automation scope is not a fixed value but a dial you adjust with data.
Epilogue — the pipeline is the shape of the team
Once you implement this post's pipeline fully, the way the team works changes.
- Developers type less code and write Issues well and review PRs well.
- Touch-up reviews go to AI, judgment reviews go to humans.
- The repetitive labor of detection, diagnosis, kicking off the fix, and redeploying goes to the system.
- Humans guard the gate of irreversible decisions.
Summed up in three core insights.
-
It's a loop, not a line. Deployment is not the end. When monitoring catches an incident and turns it back into an Issue, the system takes its own problem as input and fixes it.
-
"Automate the work, gate the decisions." Let AI write, review, and deploy code — but for irreversible decisions like merge, schema, and security, put a human or a strong automated check in the way. Fail-closed is the default.
-
Trust is accrued. Don't automate everything from the start; start with low-risk work and climb one rung at a time, watching the metrics (especially the escape rate). The automation scope is a dial you adjust with data.
Paradoxically, the ultimate purpose of all this automation is not to take humans out of the work, but to focus humans on the most important place — judgment and decisions. When the pipeline absorbs the repetitive labor, the team's thinking rises from "how do we build it" to "what and why do we build."
A 14-item checklist
- Have you identified all 7 stages and assigned an owner to each?
- Is every stage fail-closed?
- Does every stage have a rollback/cancel means?
- Are the generation agent and review agent separated (different models)?
- Is AI review wired in as a required status check?
- Are CI failure messages friendly enough for AI to read?
- Does the self-heal loop have an attempt ceiling?
- Does branch protection enforce required checks + human approval?
- Is human approval counted only from human accounts, not bots?
- Are Preview, Canary, and Production separated in stages?
- Does the
productionenvironment have required reviewers? - Does auto-verification (smoke, SLO) gate the Canary promotion?
- On an SLO violation, do auto-rollback + auto Issue creation happen (the loop)?
- Do you track the escape rate and adjust the automation scope with that data?
10 anti-patterns
- The generation agent reviews and approves its own PR.
- An AI review APPROVE counted as a human approval.
- Auto-merging application logic.
- 100% production deploy the moment a merge happens (no Canary).
- No post-deploy verification (no smoke / SLO gate).
- Auto-rollback exists but the incident doesn't come back as an Issue (the loop isn't closed).
- CI failure messages are unfriendly, so AI self-healing is impossible.
- No ceiling on the self-heal loop → cost bomb.
- External users' Issues auto-trigger the pipeline.
- Widening the automation scope without measuring the escape rate.
Next post preview
Candidates for the next post: Building your own AI code reviewer — a custom review bot that learns the team's conventions, Progressive Delivery deep dive — SLO-based auto-promotion with Argo Rollouts and Flagger, Agent orchestration — turning a multi-stage pipeline into a state machine with LangGraph.
"The best pipeline doesn't replace humans. It absorbs all the rest of the repetition so that humans only make decisions."
— From Issue to Deploy, building an automation pipeline, done.