CI/CD

在每個 Pull Request / Merge Request 上跑 OCR

CI/CD 整合如何運作

每條配方都遵循同一模式:

  1. 在 PR / MR 事件上觸發(新建、更新,或 /open-code-review 評論)。
  2. 在 runner 中安裝 ocr(runner 是臨時的,每次安裝)。
  3. 從 CI secret 配置 LLM(ocr config set)。
  4. 以區間模式跑評審、輸出機器可讀 JSON。
  5. 解析 JSON、遍歷 comments[]。
  6. 透過 provider 的 review API 回貼評論到 PR / MR。

兩類憑證:LLM 憑證(產生發現)與 PR/MR 寫 token(回貼評論)。GitHub 用 GITHUB_TOKEN 自動提供後者;GitLab 建議顯式 GITLAB_API_TOKEN。

GitHub Actions

mkdir -p .github/workflows
curl -o .github/workflows/ocr-review.yml \
  https://raw.githubusercontent.com/alibaba/open-code-review/main/examples/github_actions/ocr-review.yml

在 pull_request_target(opened)與以 /open-code-review 或 @open-code-review 開頭的 issue_comment 上觸發。透過 GitHub Pull Request Review API 把發現貼成內聯評論;無行資訊的併入摘要。

必需 secret

Secret必需說明
OCR_LLM_URL是LLM API 端點
OCR_LLM_AUTH_TOKEN是認證 token(傳給 ocr config set llm.auth_token)
OCR_LLM_MODEL否模型名——無預設,必須顯式設定
OCR_LLM_USE_ANTHROPIC否Anthropic Claude 模型設 true

客製化:傳背景脈絡

- name: Run OCR review
  env:
    PR_TITLE: ${{ github.event.pull_request.title }}
    BASE_REF: ${{ github.base_ref }}
    HEAD_REF: ${{ github.head_ref }}
  run: |
    ocr review \
      --background "$PR_TITLE" \
      --from "origin/$BASE_REF" \
      --to "origin/$HEAD_REF" \
      --format json --audience agent
安全提示:把 PR 可控的值透過 env: 傳入,不要把 ${{ }} 直接插值進 run:——GitHub 在 shell 解析前就做了文字替換,含 shell 元字元的 PR 標題會在你的 runner 上被執行。

客製化:自訂規則 / 併發 / 固定版本

ocr review --rule ./my-rules.json \
  --from "origin/$BASE_REF" --to "origin/$HEAD_REF"

ocr review --concurrency 5 \
  --from "origin/$BASE_REF" --to "origin/$HEAD_REF"

npm install -g @alibaba-group/open-code-review@1.0.0

GitLab CI

curl -o .gitlab-ci.yml \
  https://raw.githubusercontent.com/alibaba/open-code-review/main/examples/gitlab_ci/.gitlab-ci.yml

在 merge_requests 事件上觸發,在 node:20 映像中跑。內聯 Python 腳本解析 JSON、用 MR 的 versions 端點計算正確 SHA、把發現貼成 GitLab Discussion。

必需 CI/CD 變數

變數必需掩碼說明
OCR_LLM_URL是否LLM API 端點 URL
OCR_LLM_AUTH_TOKEN是是認證 token
OCR_LLM_MODEL否否模型名
GITLAB_API_TOKEN否是帶 api scope 的 token;缺失時回退 CI_JOB_TOKEN

客製化:避免每次 push 都重審

GitLab 無「僅在建立時」事件,推薦在跑評審前檢查已有 OCR note,有則跳過(省 LLM token)。完整 Python wrapper 見上游 ci.md——核心邏輯是查 MR notes 是否含 OpenCodeReview。

疑難排解

症狀原因 / 修復
Cannot find merge-base淺克隆。GitHub 保留 fetch-depth: 0;GitLab 保留 GIT_DEPTH: 0
Failed to parse OCR outputOCR_LLM_URL 或 OCR_LLM_AUTH_TOKEN 錯誤
評論落錯行評審到張貼間 diff 偏移;貼文腳本自動回退為一般 issue 評論
📖 教學解說:CI/CD 實戰

六步 CI 模式的因果邏輯

六步不是任意順序:① 觸發(PR 事件)→ ② 安裝(runner 臨時)→ ③ 配置 LLM(secret)→ ④ 跑評審(區間模式 + JSON)→ ⑤ 解析 JSON(提取 comments)→ ⑥ 回貼評論(PR API)。每步都有原因:為什麼區間模式?因為 PR 只需審 diff。為什麼 JSON?因為腳本需要結構化資料。

兩類憑證的致命混淆

LLM 憑證(產生發現)和 PR/MR 寫 token(回貼評論)是完全獨立的。LLM 憑證是你的 API key,PR 寫 token 是 GitHub/GitLab 的。GitHub 的 GITHUB_TOKEN 自動提供後者;GitLab 用 CI_JOB_TOKEN 但建議用顯式 GITLAB_API_TOKEN(scope 更完整)。混淆這兩者是 CI 故障的 #1 原因。

GitHub 的 ${{ }} 安全陷阱

GitHub 在 shell 解析前就做了 ${{ }} 的文字替換。如果 PR 標題含 $(rm -rf /) 或反引號,它會在你的 runner 上被執行。安全做法:把 PR 可控的值透過 env: 傳入,在 run: 中用 $PR_TITLE(shell 變數)而非 ${{ github.event.pull_request.title }}。

🔍 Worked Example:GitHub Actions 完整設定

Step 1 — 下載 workflow 檔

mkdir -p .github/workflows && curl -o .github/workflows/ocr-review.yml https://raw.githubusercontent.com/alibaba/open-code-review/main/examples/github_actions/ocr-review.yml

Step 2 — 在 GitHub repo Settings → Secrets 新增

OCR_LLM_URL, OCR_LLM_AUTH_TOKEN, OCR_LLM_MODEL

Step 3 — 建立 PR 觸發評審

git checkout -b fix/auth-bug && git commit --allow-empty -m 'test: trigger OCR' && git push origin fix/auth-bug

Step 4 — 在 GitHub 建立 PR,觀察 Actions tab

查看 OCR review job 的輸出

Step 5 — 在 PR 中看到內聯評論

✓ OCR 在 diff 中貼了 2 條評論

預期產出

Run OCR review
  Installing ocr...
  Configuring LLM endpoint...
  Reviewing 3 files...
  Posting 2 inline comments to PR...
  ✓ Done (45s, 12,340 tokens)

常見錯誤與診斷

錯誤訊息診斷修復
Cannot find merge-base淺克隆GitHub: fetch-depth: 0;GitLab: GIT_DEPTH: 0
Failed to parse OCR outputOCR_LLM_URL 或 OCR_LLM_AUTH_TOKEN 錯誤重設 CI secrets
評論落錯行評審到張貼間 diff 偏移貼文腳本自動回退為一般 issue 評論
${{ }} 注入攻擊PR 標題含 shell 元字元用 env: 傳入,不要直接插值進 run:

練習 / 驗收清單

  • 能說出六步 CI 模式的順序與原因
  • 能區分兩類憑證(LLM 憑證 vs PR 寫 token)
  • 能解釋為何不要把 ${{ }} 描述進 run:
  • 能設定 GitHub Actions workflow
  • 能設定 GitLab CI 並避免每次 push 重審
看完這頁你應該能說出:六步 CI 模式、兩類憑證的差別、為何不要把 ${{ }} 插值進 run:、以及 GitLab「避免每次 push 重審」的技法。

延伸閱讀:CLI 參考(JSON 輸出) · 設定 · 評審規則

① 進階真實情境 Worked Example:GitLab monorepo 的精準評審

情境

你的團隊把多個微服務收進一個 monorepo(services/api、services/worker、web)。你不想每次 push 都重審整個 repo(燒 token),也不想讓一個 web 的改動觸發對 services 的評審。你要在 GitLab CI 上做到「只審本次 MR 實際碰到的目錄」。

Step 1 — 在 pipeline 裡算出「變更的頂層目錄」

git diff --name-only "origin/$CI_DEFAULT_BRANCH"...HEAD \
  | cut -d/ -f1 | sort -u | paste -sd, -

例如輸出 services/api,web——只有這兩個子樹真的變了。

Step 2 — 用變更目錄當 scan path 跑評審

CHANGED="$(git diff --name-only origin/main...HEAD | cut -d/ -f1 | sort -u | paste -sd, -)"
ocr scan --path "$CHANGED" \
  --background "MR: $CI_MR_TITLE" \
  --format json --audience agent > review.json
cat review.json | jq -e '.status == "success"'

用 ocr scan --path 而非 ocr review 的 --from/--to——因為 monorepo 的 merge-base 常常被其他子樹的推送污染,--path 讓我們精準限定審查範圍,只對「真的變了的目錄」整檔審。

Step 3 — 貼成 GitLab Discussion

python3 - <<'PY'
import json, os
mr = os.environ["CI_MR_IID"]
data = json.load(open("review.json"))
for c in data.get("comments", []):
    # 對每個 comment 在 MR 對應檔案的行上建立 discussion
    ...  # 用 GitLab notes API POST /projects/:id/merge_requests/:iid/discussions
PY

Step 4 — 避免重複審:檢查 MR notes 是否已含 OCR 產物

curl -s --header "PRIVATE-TOKEN: $GITLAB_API_TOKEN" \
  "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MR_IID/notes" \
  | jq '[.[] | select(.body | contains("OpenCodeReview"))] | length'

大於 0 就 exit 0 跳過——省下重複的 LLM token(官方建議的「僅在建立時」替代方案)。

為什麼選這條審查路徑

monorepo 最怕「評審範圍失焦」。ocr scan --path <變更目錄> 比 range diff 更穩:不受 merge-base 漂移影響、天然限定在「這顆 MR 的責任範圍」、且對目錄內整檔審(重構搬移也能抓到)。加上「MR 已審過就跳過」的防重複邏輯,token 成本從「每次 push 全審」降到「每個 MR 只審一次、且只審受影響子樹」。

預期產出

CHANGED = services/api,web
ocr scan: 2 subtrees, 11 files reviewed, 5 comments posted
No duplicate review: next MR push skipped (note already exists)

② 深入原理擴充:CI 中評論定位的「時間軸」問題與隱患案例

為什麼評論會落錯行

OCR 在「評審當下的 commit」錨定行號。但 CI 回貼評論到 MR 時,MR 的 head 可能已經推進(作者又 push 了新 commit)。行號對不上是必然發生的——所以官方貼文腳本會先查 MR 的 versions 端點算正確 SHA,仍失敗就回退成一般 issue 評論。這不是 bug,是「評審與張貼之間的時間差」的固有問題。

「看起來通過 review 但其實有隱患」的案例

# CI pipeline 檢查「評論是否成功張貼」用這個:
cat review.json | jq -e '.status == "success"' > /dev/null \
  && echo "review OK"

這條「綠燈」只證明了 OCR 跑完了。它沒證明「評論真的貼上 MR」、也沒證明「貼的行號正確」。常見的假成功:

  • OCR_LLM_AUTH_TOKEN 過期 → 回貼步驟 403,腳本吞掉錯誤。
  • 評論定位在已過時的 commit → 貼上去後 GitLab 顯示在「錯誤的舊行」,作者看不到。
  • 全檔被過濾 → status: success 但 comments: []——CI 綠燈,實際零審查。

修正:把「成功」的定義從「OCR exit 0」擴大到「comments 非空才貼 + 貼完再 GET 回來驗證存在」。把「零檔案可審」從綠燈改成黃燈(寫進報告而非直接通過)。

③ 診斷式疑難排解

症狀可能原因解決方案
每個 push 都重審整 repo沒做「僅審變更目錄」+「已審過就跳過」用 --path 限定子樹;檢查 MR notes 是否已有 OCR 產物
評論貼到錯的 commit / 舊行張貼時 MR head 已推進用 MR versions 端點算正確 SHA;失敗回退一般評論
pipeline 綠燈但實際零審查status: success 但 comments: [](全檔被過濾)檢查 --preview;把「零檔案」設為警告而非通過
回貼步驟 403GITLAB_API_TOKEN 過期或缺 api scope用帶 api scope 的個人 token;確認 CI/CD 變數有 mask
monorepo 的 merge-base 計算錯其他子樹的推送污染了 origin/main...HEAD改用 ocr scan --path <變更目錄>,避開 merge-base 依賴

④ 進階挑戰題

  1. 挑戰一:設計一個 GitLab pipeline job,滿足「只在 MR 建立時審一次」「只審變更目錄」「評論張貼失敗時能重試而不重審」。畫出 job 之間的 dependencies 與每步的 exit code 合約。
  2. 挑戰二:GitHub 用 pull_request_target 的原因是什麼?它比 pull_request 多的權限帶來了什麼安全風險?(提示:與 ${{ }} 注入、以及 workflow 對 PR 的信任邊界有關)
  3. 挑戰三:你的團隊用「先審後合併」策略,但合規要求「所有 PR 必須有 OCR 評論」。設計一個檢查機制,避免「開發者開個空 MR 刷綠燈」。

① 專案級端到端 Worked Example:全公司「統一評審閘道」建置

情境

公司有 20+ 個 repo 要接 OCR,但每個 repo 的 workflow 都是複製貼上、設定各自漂移。你要建立統一評審閘道:一個可重用 workflow template(含門檻、報告、失敗處理),全部 repo 只改一行引用就接入。

Step 1 — 建立可重用的 composite action(GitHub)

# .github/actions/ocr-gate/action.yml
runs:
  using: composite
  steps:
    - run: |
        npm install -g @alibaba-group/open-code-review@${{ inputs.version }}
        ocr review \
          --from "origin/${{ github.base_ref }}" \
          --to "origin/${{ github.head_ref }}" \
          --background "${{ github.event.pull_request.title }}" \
          --format json --audience agent > ocr-report.json
      env:
        OCR_LLM_URL: ${{ secrets.OCR_LLM_URL }}
        OCR_LLM_AUTH_TOKEN: ${{ secrets.OCR_LLM_AUTH_TOKEN }}
        OCR_LLM_MODEL: ${{ inputs.model }}
    - run: |
        # 門檻: high-severity 評論 > 0 就讓 PR 卡住(blocking gate)
        python3 ocr-gate.py ocr-report.json

Step 2 — 每個 repo 只引用它(一行的接入成本)

# 各 repo 的 ocr-review.yml
jobs:
  ocr-gate:
    runs-on: ubuntu-latest
    permissions: { contents: read, pull-requests: write }
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }        # 淺克隆會壞 merge-base
      - uses: your-org/ocr-gate/.github/actions/ocr-gate@v1
        with: { model: claude-opus-4-6 }

Step 3 — 閘道腳本:把「綠燈」的定義寫對

# ocr-gate.py 的核心判斷(別只看 exit code)
data = json.load(open("ocr-report.json"))
assert data["status"] == "success", "OCR failed"
assert data["summary"]["files_reviewed"] > 0, "Zero files reviewed — check scope"
blocking = [c for c in data["comments"] if c.get("severity") in ("high", "critical")]
if blocking:
    print("BLOCKED:", len(blocking), "high/critical findings")
    sys.exit(1)  # PR 卡住

Step 4 — 上送報告與指標

# 把 ocr-report.json 存成 artifact + 送 telemetry(統一監控)
OCR_ENABLE_TELEMETRY: "1"
OTEL_EXPORTER_OTLP_ENDPOINT: collector:4317
OTEL_SERVICE_NAME: ocr-gate

為什麼選這條路徑

「統一閘道」的價值是把規則收斂到一處:門檻邏輯、secret 命名、報告格式都只定義一次。每個 repo 的接入成本降到「複製 6 行」——導入阻力最小化。真正的關鍵是把「綠燈」定義寫對(Step 3):不檢查 files_reviewed > 0 的話,「全檔被過濾」也會綠燈——那就是「空審查通過」的假陽性。

預期產出

Template v1 上線 → 20 repo 一週內全接
閘道統計: 每月擋下 40+ 顆含 high-severity 的 PR
假陽性檢查: 零「空審查通過」事件(files_reviewed=0 時確實卡住)

② 效能 / 品質 / 安全深度:CI 閘道三面向

效能:閘道延遲 vs 開發者體驗

評審閘道是「同步阻塞」的——它在 PR 合併路徑上,延遲直接影響開發者。控制方式:① 只在 opened 與 synchronize 事件觸發,避免空跑;② 先跑「已審過就跳過」的檢查(避免每 push 重審,GitLab 的 note 檢查、GitHub 的 reaction/label 標記);③ 用 --concurrency 限制撞 rate limit 的重試放大。目標:p95 閘道時長 < 3 分鐘。

品質:閘道品質 = 「評論是否被看到」

閘道最容易失敗的地方是「評論張貼了但沒人看」:評論貼在舊 commit 的行號上、或作者只看 CI 綠燈不看 inline comment。品質措施:① 用 PR versions/commit SHA 修正張貼位置;② 把 summary 貼成 PR 頂部 comment(不只 inline);③ 用 required status check 強制「閘道過 + 評論被讀」兩者都成立才可 merge。

安全:閘道是高權限目標

閘道 workflow 有 PR 寫權限——它是供應鏈攻擊的高價值目標。安全措施:① pull_request_target 上不 checkout PR 的 code(避免 PR 內容注入 workflow);② 把 PR 可控值(標題)用 env: 傳入而非 ${{ }} 插值;③ workflow 的 permissions 用最小集(contents: read + pull-requests: write),不給 actions: write 或 secrets 全域讀取。

③ 文件間比較對照表

面向本文(CI/CD)相關文差異說明
自動化層級PR/MR 自動觸發 + 回貼評論integrations.html整合頁涵蓋互動式 agent(skill/command/OpenCode 工具);CI/CD 是「無人的批次流程」
評論張貼透過 PR/MR review API 回貼delegate.html委託模式由宿主 Agent 自己回報,不經 OCR 的張貼模組——CI 的張貼是「確定性工程」的一部分
失敗處理merge-base / 解析失敗的診斷表faq.htmlFAQ 是「人類除錯」,CI 頁教「把除錯寫進 pipeline」(exit code + 門檻)
監控CI 延遲與成本telemetry.htmlCI 頁關注「每次 PR 的延遲」,telemetry 頁教「長期趨勢儀表板」——閘道把兩者接起來

④ 互動式檢核清單

進階驗收:你能建置並維運一套評審閘道嗎?

  • - [ ] 能用 composite action / template 讓新 repo 一行接入閘道
  • - [ ] 能把「綠燈」定義寫成腳本(含 files_reviewed > 0 檢查)
  • - [ ] 能避免每 push 重審(GitHub 事件選擇 / GitLab note 檢查)
  • - [ ] 能修正「評論貼到舊 commit」的行號偏移
  • - [ ] 能用最小 permissions + env 傳值抵禦 workflow 注入攻擊
  • - [ ] 能用 required status check 強制「評審 + 讀取」都通過才能 merge