Claude Code 리뷰를 통과해야만 배포되도록 구현: 코드 내용의 지문으로 확인
요약
Claude Code 검토 과정을 배포 파이프라인에 강제하는 방법을 설명합니다. 단순히 규칙을 남기는 것이 아니라, 코드 내용의 '지문(fingerprint)'을 생성하고 이를 `.git/`에 기록한 후, `predeploy` 훅에서 이 지문을 비교하여 불일치 시 배포를 중단시킵니다.
핵심 포인트
- 코드 내용 기반의 지문(fingerprint) 생성 방식을 사용합니다.
- 지문은 임시 인덱스를 활용해 커밋 번호와 무관하게 생성됩니다.
- `predeploy` 훅을 이용해 검토 전후 코드 변경 여부를 강제 확인합니다.
- 이 모든 로직을 Node.js 스크립트 하나로 구현하여 프로젝트에 적용했습니다.
Claude Code로 작성한 코드를 실제 서비스에 배포하기 전에 Codex에게 검토(review) 받도록 했습니다. 하지만 단순히 '검토 후에 배포한다'는 규칙을 코드로 남겨 놓기만 해서는, 급하게 작업하는 날에는 이 단계를 건너뛰게 될 위험이 있습니다. 그래서 검토를 통과하지 못한 코드는 배포가 멈추도록 만들었습니다.
구현한 내용은 세 가지입니다.
- 검토를 실행하고, 통과하면 '현재 코드 내용의 지문(fingerprint)'을
.git/에 기록합니다. npm run deploy직전에 (predeploy) 현재 코드의 지문과 기록된 지문을 비교합니다. 다르면 배포를 중단시킵니다.- 검토가 실제로 지정한 모델로 작동했는지 Codex 세션 기록을 통해 확인합니다.
이 모든 과정은 Node.js 스크립트 1개(약 260줄)로 구현되었으며, 저희 프로젝트 전체에서 사용하고 있습니다.
1. 지문은 커밋 번호가 아닌 내용으로 생성한다
처음에는 '검토를 완료한 커밋의 번호를 기록하는' 방법을 생각했습니다. 하지만 이 방식으로는 다음 두 가지 문제에 직면합니다.
- 검토 후에 커밋하면, 번호가 바뀌어 배포가 멈춥니다 (실제 내용은 동일함에도 불구하고).
- 커밋하지 않은 변경 사항이 있으면, 번호는 같더라도 내용이 다를 수 있습니다.
그래서 작업 중인 파일의 내용을 기반으로 지문을 생성하기로 했습니다. git add -A를 통해 git의 임시 인덱스에 추가하고 write-tree를 실행하면, 그때 시점의 전체 파일 트리의 번호를 얻을 수 있습니다. .gitignore에 의해 제외된 파일은 포함되지 않습니다.
/** 작업 중인 파일 내용의 지문 (임시 index로 git add -A 후 write-tree) */
function fingerprint() {
const idx = join(gitDir, "review-gate-index.tmp");
...
GIT_INDEX_FILE을 임시 파일로 지정하기 때문에, 평소의 스테이징 상태(git add한 상태)에는 영향을 주지 않습니다. 따라서 검토 후에 커밋하더라도 내용이 같다면 지문은 동일합니다.
2. 배포 전에 비교한다
검토가 통과하면, 그 지문을 .git/review-stamp.json에 기록합니다. 이 파일은 .git/ 안에 있으므로 커밋되지 않습니다.
배포 측에서는 package.json의 predeploy 단계에서 단순히 비교만 하면 됩니다.
{
"scripts": {
"review": "node "$HOME/.claude/scripts/review-gate.mjs" run",
...
}
if (cmd === "check") {
const stamp = readJson(STAMP);
const now = fingerprint();
...
npm은 deploy 전에 자동으로 predeploy를 실행하므로, npm run deploy를 우회할 수 없습니다. 또한, wrangler deploy를 직접 실행하는 것도 다른 훅에서 막아두었습니다.
검토 과정에서 지적이 나온 경우에는 두 가지로 나누어 처리했습니다.
- 수정해야 할 경우: 코드가 변경되므로, 검토를 다시 실행합니다.
- 코드 내용으로 확인했고 수정할 필요가 없다고 판단한 경우: 그 이유를 작성하여 기록합니다 (`ack
// 이 리뷰에서 작동한 세션(시작 시간 이후 및 이 리포지토리에서 작동한 것)으로부터 모델과 추론의 강도를 읽음
const model = (text.match(/"model":"([^"]+)"/) ?? [])[1] ?? "?";
const effort = (text.match(/"effort":"([^"]+)"/) ?? [])[1] ?? "?";
...
두 가지 역할이 모두 갖춰지고, 지정된 모델로 작동하며, 결과가 APPROVE 또는 COMMENT일 때만 표시를 기록합니다. 둘 중 하나라도 빠지면 리뷰는 불성립으로 간주하여 표시를 남기지 않습니다.
4. 통과하지 못한 경우의 처리
사소한 부분이지만, 다음과 같은 경우에도 "통과"로 간주하지 않도록 합니다.
사용량 제한(利用上限)으로 중단: Codex가 도중에 끝나면, 출력에 무엇이 쓰여 있든 불성립입니다. 사용량 제한인지 여부는 출력의 마지막 부분에서만 확인합니다 (리포트 본문에 "usage limit"이라는 문구가 나올 수 있기 때문).
리뷰 중에 코드가 변경된 경우: 실행 전과 후의 표시가 다르면 불성립입니다.
같은 내용으로 리뷰를 재실시했으나 통과하지 못한 경우: 이전에 통과했을 때의 표시는 삭제합니다. 이전 합격 상태 그대로 배포되는 것을 방지하기 위함입니다.
요약 (Summary)
- "리뷰 후 제출"은, 규칙으로 적어 놓기만 해서는 빠집니다. 배포 직전에 멈추도록 처리했습니다.
- 표시는 커밋 번호가 아니라 작업 중인 파일의 내용에서 만듭니다(임시 index를 사용하여
git add -A→write-tree). 커밋해도 내용이 같다면 통과합니다. 표시는.git/안에 두고,predeploy시 비교합니다. 지정된 모델로 작동했다고 단정할 수 없으므로, Codex 세션 기록을 통해 실제 모델을 확인해야 합니다. - 사용량 제한, 리뷰 중 변경, 재실시 실패 등은 모두 "통과하지 못함"으로 간주합니다.
토론 (Discussion)

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기