· ci / github-actions / claude-code

Claude Code trong CI — review PR tự động bằng GitHub Actions

Cài Claude Code reviewer vào mọi PR bằng claude-code-action@v1. Dưới 10 phút, tốn từ $0.01 đến $0.15 mỗi PR, bắt được bug thực trước khi merge.

Bởi

2.283 từ · 12 phút đọc

anthropics/claude-code-action@v1 đưa Claude Code reviewer vào mọi PR. Nó comment trực tiếp trên pull request, tốn từ $0.01 đến $0.15 mỗi lần review với Sonnet 4.6, và chỉ mất khoảng 10 phút để cài xong. Nếu bạn đã thấy các hướng dẫn dùng direct_prompt: trong file YAML — đó là cú pháp của bản beta cũ. Ở v1, field đúng là prompt:.

Bài này dành cho ai

Các team dùng GitHub với Actions được bật, muốn có một lớp review code tự động mà không cần trả tiền cho một tool chuyên dụng. Bạn cần một Anthropic API key và khoảng 10 phút. Đây không phải là thay thế cho code review của người — đây là lần đọc đầu tiên, giúp bắt những lỗi mà người ta thường bỏ qua khi đang mệt hoặc vội.

Nếu bạn chưa quen với Claude Code, đánh giá Claude Code 2026 cho một cái nhìn tổng quan trước khi tích hợp CI.

Tại sao nên tự động hóa Claude Code trong CI

Ba điểm mà nó bắt được ổn định:

Logic bug trước khi người review. Claude đọc toàn bộ diff và đánh dấu những thứ như lỗi off-by-one, error path chưa được xử lý, và null handling không nhất quán — loại lỗi mà người ta hay bỏ qua khi review PR 400 dòng vào cuối ngày. Nó không bị mệt.

Style drift và chuẩn code bị lệch. Cho nó một system prompt mô tả quy ước của team, nó sẽ cảnh báo khi code đi lệch. Không phải linter — linter lo phần format. Đây là phần pattern: “chúng ta luôn validate trước khi mutate state,” “service này không bao giờ gọi synchronous HTTP trong request handler.” Nó bắt được những vi phạm ở tầng cao hơn mà linter không nhìn thấy.

Anti-pattern bảo mật. Hardcoded secret, SQL string concatenation, thiếu input validation, dùng flag nguy hiểm. Không phải SAST scanner, nhưng nó bắt được những cái rõ ràng và cảnh báo trước khi chúng vào main.

Tính chi phí: với giá Sonnet 4.6, một PR 300 dòng tốn khoảng $0.05. Năm mươi PR một tháng là dưới $5. Team 3–5 người với 10–15 PR mỗi tuần vào khoảng $15–25/tháng. So với giờ công của một người review hay bất kỳ SaaS review code nào thì không đáng kể.

Điều kiện tiên quyết

  • Một Anthropic API key. Lấy tại console.anthropic.com ở mục API Keys.
  • Một GitHub repository với Actions được bật. Gói miễn phí cũng dùng được.
  • Chỉ vậy thôi. Claude Code không cần được cài trên máy local cho workflow này — action tự cài nó trong runner.

Bước 1 — Thêm API key vào GitHub secret

Trong repo của bạn: Settings → Secrets and variables → Actions → New repository secret.

Tên: ANTHROPIC_API_KEY
Giá trị: key từ console.anthropic.com

Giữ tên y chang như trên. Workflow tham chiếu nó bằng ${{ secrets.ANTHROPIC_API_KEY }} — gõ sai một chữ sẽ thành chuỗi rỗng im lặng, nghĩa là Claude nhận authentication error nhưng workflow vẫn báo thành công.

Lỗi thường gặp: nếu bỏ qua bước này nhưng vẫn khai báo secret trong workflow, action sẽ thoát với lỗi API key not found. Thông báo lỗi rõ ràng; cách sửa là thêm secret. Kiểm tra tab Actions trong run bị lỗi để xem toàn bộ log.

Bước 2 — Viết file workflow

Tạo .github/workflows/claude-review.yml:

name: Claude Code Review
on:
  pull_request:
    types: [opened, synchronize]
    paths-ignore:
      - '*.md'
      - 'docs/**'

jobs:
  review:
    runs-on: ubuntu-latest
    # Bỏ qua draft PR — không tốn tiền cho đến khi PR thực sự
    if: github.event.pull_request.draft == false
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 1
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          prompt: "Review this PR for bugs, security issues, and code quality. Be specific and concise. Flag anything that would block a merge."
          claude_args: "--model claude-sonnet-4-6 --max-turns 5 --max-budget-usd 0.50"

Ba điểm cần lưu ý:

prompt: không phải direct_prompt: — action của bản beta cũ dùng direct_prompt:. Gần như tất cả các hướng dẫn viết trước tháng 8 năm 2025 đều sai chỗ này. Action v1 âm thầm bỏ qua các input không hợp lệ, nên nếu bạn copy từ hướng dẫn cũ, action sẽ rơi vào chế độ @claude mention-trigger và không làm gì cả với sự kiện pull_request. Field đúng hiện tại là prompt:.

--max-turns 5 — không có cờ này, một phiên agentic bị lạc có thể chạy hơn 20 lượt trên PR lớn. Năm lượt là đủ cho review đọc-không-ghi.

--max-budget-usd 0.50 — giới hạn tiền cứng cho mỗi lần chạy. Action thoát sạch khi đạt giới hạn thay vì tiếp tục tốn tiền trên diff quá lớn.

Workflow này còn hỗ trợ chế độ tương tác: nếu ai đó comment @claude trên PR hoặc issue, Claude trả lời trực tiếp trong thread. Để bật, thêm các trigger sau bên cạnh event pull_request:

on:
  pull_request:
    types: [opened, synchronize]
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]

Với các trigger tương tác, thêm issues: write vào block permissions.

Bước 3 — Chạy thử và đọc kết quả

Push một commit lên PR không phải draft. Workflow kích hoạt khi nhận pull_request + synchronize. Sau 30–90 giây, Claude post một review comment trực tiếp trên PR.

Kỳ vọng từ lần chạy đầu:

Định dạng comment thay đổi tùy vào những gì Claude tìm thấy. Với diff có vấn đề rõ ràng, nó tạo ra danh sách có gạch đầu dòng với tham chiếu file và số dòng. Với diff sạch, nó có thể post tóm tắt ngắn hoặc không comment gì — im lặng là ổn, không có comment nghĩa là không có gì bị đánh dấu.

Tab Actions hiển thị toàn bộ output của lần chạy. Nếu bạn thêm --output-format json vào claude_args, output của bước sẽ có field total_cost_usd để theo dõi:

claude_args: "--model claude-sonnet-4-6 --max-turns 5 --output-format json"

Xem raw log để thấy chi phí từng lần chạy. Theo dõi vài tuần, bạn sẽ biết prompt của mình đang quá rộng (token cao, comment chung chung) hay đã đúng tầm.

Nếu workflow chạy nhưng không có comment: kiểm tra block permissionspull-requests: write không. Action cần quyền write để post. Đây là lỗi im lặng phổ biến nhất với setup mới — workflow thoát với code 0 nhưng không làm gì.

Bước 4 — Chỉnh prompt

prompt: mặc định trong ví dụ trên là cố ý generic. Hai đòn bẩy làm thay đổi kết quả nhiều nhất:

--append-system-prompt bổ sung hướng dẫn vào system prompt của Claude mà không thay thế context có sẵn của action:

claude_args: >-
  --model claude-sonnet-4-6
  --max-turns 5
  --append-system-prompt "This is a TypeScript/Node.js codebase.
  Flag: unchecked promises, missing error boundaries, type assertions that bypass the type system.
  Skip style comments — we have a linter for those."

Thu hẹp system prompt theo stack của bạn giúp giảm noise. Prompt “review for bugs” chung chung sẽ comment về Python idiom trong một TypeScript codebase, hoặc đánh dấu thứ tự import mà formatter của bạn đã tự lo. Ràng buộc càng cụ thể, tỷ lệ signal-to-noise trong output càng cao.

--model kiểm soát trực tiếp đánh đổi giữa chi phí và chất lượng:

# Codebase liên quan đến bảo mật, $0.50/PR là chấp nhận được:
claude_args: "--model claude-opus-4-8 --max-turns 8 --max-budget-usd 1.00"

# Startup tốc độ cao, giới hạn $0.03/PR:
claude_args: "--model claude-haiku-4-5-20251001 --max-turns 3 --max-budget-usd 0.10"

# Chuẩn: cân bằng chi phí và độ phủ
claude_args: "--model claude-sonnet-4-6 --max-turns 5 --max-budget-usd 0.50"

Sonnet 4.6 rẻ hơn Opus khoảng 40% và bắt được hầu hết các vấn đề. Haiku bỏ sót các logic bug tinh tế hơn nhưng đủ dùng cho kiểm tra style và bảo mật đơn giản. Dùng Opus khi codebase đủ phức tạp để bạn cần một reviewer kỹ lưỡng và sẵn sàng trả giá đó.

Chi phí và giới hạn rate

Điều chỉnhCách càiHiệu quả
Model tier--model claude-sonnet-4-6Sonnet ≈ rẻ hơn Opus 40%
Giới hạn lượt--max-turns 5Dừng cứng ở 5 bước agentic
Giới hạn tiền--max-budget-usd 0.50Thoát sạch khi đạt giới hạn
Lọc eventtypes: [opened] (không phải synchronize)Một lần mỗi PR, không theo mỗi push
Lọc pathpaths-ignore: ['*.md', 'docs/**']Bỏ qua thay đổi chỉ có docs
Lọc draftif: github.event.pull_request.draft == falseKhông tốn tiền cho draft
Concurrencyconcurrency: cancel-in-progress: trueHuỷ run đang xếp hàng khi có push mới
Timeouttimeout-minutes: 10Kết thúc job bị treo

Thay đổi về billing — 15 tháng 6 năm 2026: Nếu bạn xác thực qua CLAUDE_CODE_OAUTH_TOKEN (subscription OAuth, từ claude setup-token) thay vì ANTHROPIC_API_KEY, usage giờ được trừ từ credit Agent SDK hàng tháng — gói Pro: $20/tháng, Max 5×: $100/tháng, Max 20×: $200/tháng. Người dùng API key không bị ảnh hưởng; họ vẫn thanh toán theo giá token API. Với CI, dùng ANTHROPIC_API_KEY. Nó hoạt động với --bare, không có giới hạn credit subscription, và là path xác thực mà Anthropic khuyến nghị cho non-interactive use.

Bảo mật

Prompt injection là một attack surface đã được ghi nhận cho AI agent trên GitHub Actions. Một issue body được tạo ra cẩn thận có thể inject hướng dẫn khiến Claude đọc ACTIONS_ID_TOKEN_REQUEST_TOKEN từ môi trường process và đổi nó lấy GitHub token có quyền write. Anthropic đã vá bao gồm xác minh human actor, scrubbing biến môi trường, và một wrapper gh tùy chỉnh chặn các pattern exfiltration — kiểm tra trang releases GitHub để xác nhận bạn đang chạy phiên bản mới nhất. uses: anthropics/claude-code-action@v1 tự kéo bản release v1 mới nhất. Chỉ pin vào SHA cụ thể nếu policy bảo mật của bạn yêu cầu.

Đừng dùng --dangerously-skip-permissions trong CI. Flag này tắt toàn bộ guardrail và cho phép Claude thực thi lệnh shell tùy ý mà không cần xác nhận. Nhiều hướng dẫn đề xuất nó để bỏ qua permission prompt, nhưng đây là sự đánh đổi sai trong CI khi Claude đang đọc nội dung diff không đáng tin. Dùng --allowedTools thay vào:

# Chỉ cấp quyền cần thiết cho review đọc-không-ghi
claude_args: '--allowedTools "Read" "Bash(git log *)" --max-turns 5'

Với code review đọc-không-ghi, Read là đủ. Thêm Bash(git log *) nếu bạn muốn Claude xem lịch sử commit. Không cần cấp thêm gì khác.

Fork PR: Hiện không có cấu hình nào an toàn để chạy workflow này trên fork PR. Event pull_request chặn secret với workflow được trigger từ fork, nên Claude không thể xác thực. Event pull_request_target thì có expose secret, nhưng nó chạy code workflow của fork với secret của bạn trong scope — một attack vector đã được biết đến. Mặc định an toàn: bỏ qua fork PR và để người review xử lý thủ công.

Xử lý sự cố

1. Không có PR comment, workflow thoát sạch.
Thiếu quyền pull-requests: write. Đây là lỗi phổ biến nhất với setup mới. Thêm quyền và chạy lại.

2. Lỗi “API key not found”.
Secret chưa được tạo, đặt tên khác, hoặc workflow không có block env: để truyền nó. Kiểm tra tên secret khớp chính xác (phân biệt chữ hoa-thường), rồi kiểm tra env ở cấp step nếu action yêu cầu.

3. Prompt có ký tự đặc biệt làm action báo lỗi.
Giá trị prompt: không được quote sẽ lỗi khi chuỗi chứa dấu hai chấm hay dấu ngoặc vuông. Dùng YAML block scalar:

prompt: |
  Review this PR for bugs and security issues.
  Flag: unchecked promises, SQL string concatenation, missing auth checks.

4. Workflow kích hoạt mỗi lần push và tốn nhiều tiền.
types: [opened, synchronize] kích hoạt mỗi lần commit được push lên PR. Đổi thành types: [opened] để review một lần mỗi PR, hoặc thêm concurrency để chỉ push mới nhất mới trigger run được trả tiền:

concurrency:
  group: claude-review-${{ github.event.pull_request.number }}
  cancel-in-progress: true

5. Review comment rỗng hoặc trả về null.
Lỗi ngẫu nhiên đôi khi xảy ra — exit code 0, kết quả rỗng. Kiểm tra output rõ ràng:

RESULT=$(claude -p "..." --output-format json | jq -r '.result')
if [ -z "$RESULT" ] || [ "$RESULT" = "null" ]; then
  echo "Claude returned empty output — check the Actions log"
  exit 1
fi

Hoặc dùng field result từ JSON output làm guard: jq -e '.result != null' thoát với code 1 khi null, khiến workflow step bị lỗi.

Bước tiếp theo

Để dùng Claude Code cho việc tự động fix thay vì chỉ review đọc-không-ghi, xem các pattern --allowedTools Edit,Write trong tài liệu headless mode chính thức. Lưu ý rằng điều này cấp quyền write vào repository từ bên trong CI runner — hãy giới hạn allowlist cẩn thận.

Với các CI system không phải GitHub: cách tiếp cận claude --bare -p tương tự hoạt động trên GitLab CI/CD, Bitbucket Pipelines, và bất kỳ CI nào có shell access. Bạn chạy CLI trực tiếp thay vì qua Action wrapper. Đặt ANTHROPIC_API_KEY làm biến môi trường, dùng --bare để bỏ qua việc load local context, và thêm --allowedTools Read để giữ ở chế độ đọc-không-ghi. Hỗ trợ GitLab CI/CD được ghi tại code.claude.com/docs/en/gitlab-ci-cd.

Nếu bạn chạy monorepo, xem cách thiết lập Claude Code cho monorepo trước — CI context scoping với monorepo có một vài điểm khác biệt. Muốn tự động hóa sâu hơn ngoài phạm vi CI, Claude Code Hooks là bước tiếp theo tự nhiên.