每條配方都遵循同一模式:
/open-code-review 評論)。ocr(runner 是臨時的,每次安裝)。ocr config set)。comments[]。兩類憑證:LLM 憑證(產生發現)與 PR/MR 寫 token(回貼評論)。GitHub 用 GITHUB_TOKEN 自動提供後者;GitLab 建議顯式 GITLAB_API_TOKEN。
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 | 必需 | 說明 |
|---|---|---|
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
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
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。
| 變數 | 必需 | 掩碼 | 說明 |
|---|---|---|---|
OCR_LLM_URL | 是 | 否 | LLM API 端點 URL |
OCR_LLM_AUTH_TOKEN | 是 | 是 | 認證 token |
OCR_LLM_MODEL | 否 | 否 | 模型名 |
GITLAB_API_TOKEN | 否 | 是 | 帶 api scope 的 token;缺失時回退 CI_JOB_TOKEN |
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 output | OCR_LLM_URL 或 OCR_LLM_AUTH_TOKEN 錯誤 |
| 評論落錯行 | 評審到張貼間 diff 偏移;貼文腳本自動回退為一般 issue 評論 |
六步不是任意順序:① 觸發(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 在 shell 解析前就做了 ${{ }} 的文字替換。如果 PR 標題含 $(rm -rf /) 或反引號,它會在你的 runner 上被執行。安全做法:把 PR 可控的值透過 env: 傳入,在 run: 中用 $PR_TITLE(shell 變數)而非 ${{ github.event.pull_request.title }}。
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 output | OCR_LLM_URL 或 OCR_LLM_AUTH_TOKEN 錯誤 | 重設 CI secrets |
評論落錯行 | 評審到張貼間 diff 偏移 | 貼文腳本自動回退為一般 issue 評論 |
${{ }} 注入攻擊 | PR 標題含 shell 元字元 | 用 env: 傳入,不要直接插值進 run: |
${{ }} 插值進 run:、以及 GitLab「避免每次 push 重審」的技法。延伸閱讀:CLI 參考(JSON 輸出) · 設定 · 評審規則
你的團隊把多個微服務收進一個 monorepo(services/api、services/worker、web)。你不想每次 push 都重審整個 repo(燒 token),也不想讓一個 web 的改動觸發對 services 的評審。你要在 GitLab CI 上做到「只審本次 MR 實際碰到的目錄」。
git diff --name-only "origin/$CI_DEFAULT_BRANCH"...HEAD \
| cut -d/ -f1 | sort -u | paste -sd, -
例如輸出 services/api,web——只有這兩個子樹真的變了。
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 讓我們精準限定審查範圍,只對「真的變了的目錄」整檔審。
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
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)
OCR 在「評審當下的 commit」錨定行號。但 CI 回貼評論到 MR 時,MR 的 head 可能已經推進(作者又 push 了新 commit)。行號對不上是必然發生的——所以官方貼文腳本會先查 MR 的 versions 端點算正確 SHA,仍失敗就回退成一般 issue 評論。這不是 bug,是「評審與張貼之間的時間差」的固有問題。
# CI pipeline 檢查「評論是否成功張貼」用這個:
cat review.json | jq -e '.status == "success"' > /dev/null \
&& echo "review OK"
這條「綠燈」只證明了 OCR 跑完了。它沒證明「評論真的貼上 MR」、也沒證明「貼的行號正確」。常見的假成功:
OCR_LLM_AUTH_TOKEN 過期 → 回貼步驟 403,腳本吞掉錯誤。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;把「零檔案」設為警告而非通過 |
| 回貼步驟 403 | GITLAB_API_TOKEN 過期或缺 api scope | 用帶 api scope 的個人 token;確認 CI/CD 變數有 mask |
| monorepo 的 merge-base 計算錯 | 其他子樹的推送污染了 origin/main...HEAD | 改用 ocr scan --path <變更目錄>,避開 merge-base 依賴 |
pull_request_target 的原因是什麼?它比 pull_request 多的權限帶來了什麼安全風險?(提示:與 ${{ }} 注入、以及 workflow 對 PR 的信任邊界有關)公司有 20+ 個 repo 要接 OCR,但每個 repo 的 workflow 都是複製貼上、設定各自漂移。你要建立統一評審閘道:一個可重用 workflow template(含門檻、報告、失敗處理),全部 repo 只改一行引用就接入。
# .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
# 各 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 }
# 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 卡住
# 把 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 時確實卡住)
評審閘道是「同步阻塞」的——它在 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.html | FAQ 是「人類除錯」,CI 頁教「把除錯寫進 pipeline」(exit code + 門檻) |
| 監控 | CI 延遲與成本 | telemetry.html | CI 頁關注「每次 PR 的延遲」,telemetry 頁教「長期趨勢儀表板」——閘道把兩者接起來 |