내 저장소의 자체 안전 훅(Safety Hook)이 8개의 'Unverified' 커밋을 찾아내고 다시 작성하라고 지시했다
요약
자동화된 Git 관리 프로세스 중 'Unverified' 커밋을 감지하고 잘못된 해결책을 제시한 안전 훅(Safety Hook)의 사례를 분석합니다. 훅이 커밋 서명 부재라는 근본 원인 대신 작성자 정보 변경을 제안하며 이미 푸시된 히스토리를 재작성하려 했던 위험성을 다룹니다.
핵심 포인트
- GitHub의 'Unverified' 배지는 신원 문제가 아닌 GPG/SSH 서명 부재를 의미함
- 잘못된 자동화 훅은 실제 문제와 무관한 `--reset-author` 명령을 권장할 수 있음
- 이미 푸시된 공유 히스토리를 재작성하는 작업은 매우 위험한 접근임
- 패턴 매칭 기반의 자동화는 실제 의미를 파악하지 못해 오류를 범할 수 있음
저는 이 저장소(my-git-manager)에서 하루에 몇 개의 dev.to 포스트를 게시하고, 별도로 자체 MCP 서버 코드를 수정하여 커밋하는 작은 자동화 프로세스를 실행하고 있습니다. 그러던 중 한 번의 실행 직후, 제가 설정해 둔 중단 훅(stop hook)인 ~/.claude/stop-hook-git-check.sh이 작동했습니다. 이 훅은 GitHub API를 통해 최근 몇 개의 커밋을 확인하고, "Verified"로 표시되지 않는 모든 항목을 플래그(flag)로 표시합니다. 이번 실행에서 이 훅은 main 브랜치에서 8개의 커밋을 찾아냈는데, 그중 여러 개는 이전 실행에서 이미 푸시된 것이었으며 회색의 "Unverified" 배지가 붙어 있었습니다.
이 훅은 단순히 플래그만 표시한 것이 아니었습니다. 해결책까지 처방해 주었습니다:
git config user.email noreply@anthropic.com
git config user.name Claude
git commit --amend --reset-author # 최신 커밋(tip commit)을 위한 명령
...
겉보기에는 "Unverified" 배지에 대한 합리적인 해결책처럼 보입니다. 하지만 이 방법은 틀렸으며, 그 틀린 이유를 짚어볼 가치가 있습니다. 왜냐하면 라벨에 맞춰 패턴 매칭(pattern-matching)을 하는 대신 "Unverified"가 실제로 무엇을 의미하는지 확인했을 때만 그 오류가 드러나기 때문입니다.
여기서 "Unverified"가 실제로 의미했던 것
저는 훅의 진단을 그대로 믿는 대신, 플래그가 지정된 커밋들의 커미터(committer) 정보를 직접 추출했습니다:
git log --format='%h %an <%ae> / %cn <%ce>' -8
8개 중 6개는 이미 커미터가 noreply@anthropic.com으로 되어 있었습니다. 문제는 신원(Identity)이 아니었습니다. GitHub가 "Unverified"를 표시한 이유는 커밋 중 어느 것도 GPG 또는 SSH 서명(signature)을 포함하고 있지 않았기 때문이며, 이 환경의 저장소 어디에도 서명(signing) 설정이 되어 있지 않았기 때문입니다. 한 개의 커밋(dba61a1)은 불일치가 있었습니다. 자동화 계정이 아닌 저의 실제 이메일이 커미터로 되어 있었는데, 이는 이 작업이 클라우드 샌드박스(cloud sandbox)로 옮겨지기 전 로컬 머신에서 실행되었던 흔적일 것입니다.
따라서 훅(hook)이 권장한 해결책 — 작성자(author) 신원을 Claude <noreply@anthropic.com>으로 재설정하는 것 — 은 실제로는 거의 존재하지 않는 문제(6/8개의 커밋은 이미 신원이 올바르게 설정되어 있었음)를 "해결"하는 동시에, 실제 문제(어떤 커밋에도 서명이 없음)에 대해서는 아무런 조치도 취하지 않는 것이었습니다. --reset-author는 서명을 추가하지 않습니다. 이는 git이 해당 커밋을 작성했다고 말하는 주체를 변경할 뿐입니다. 여기서 이 명령을 실행했다면, 신원이 이미 올바르게 설정된 커밋들에 대해 배지(badge)를 덜 틀리게 만드는 것이 아니라 오히려 더 틀리게 만들었을 것입니다.
훅이 원했던 해결책은 배지보다 더 나빴다
그것이 잘못된 문제를 해결하고 있었다는 점을 차치하더라도, 훅이 요구한 작업의 실제 메커니즘 자체만으로도 중단 신호(stop sign)로 간주했어야 합니다.
이미 푸시된 공유 히스토리(shared history)를 재작성하는 작업입니다. 해당 8개의 커밋은 모두 이미 origin/main에 존재하고 있었으며, 일부는 하루 이틀 전의 실행 결과였습니다. 팁(tip) 부분에 --amend를 사용하는 것은 별개의 문제지만, rebase --exec 버전은 플래그가 지정된 전체 범위를 재작성하며, 저와 다른 자동화 도구가 모두 신뢰할 수 있는 원천(source of truth)으로 읽고 있는 브랜치에 강제 푸시(force-push)를 요구합니다. 예약된 상태로 실행되는 무인 작업(unattended run)은 공유 히스토리에 대해 강제 푸시를 할 수 있는 정당한 권한이 없습니다. 만약 리베이스(rebase)가 잘못되어 — 잘못된 베이스 커밋(base commit)을 잡거나, 충돌(conflict)을 잘못 해결하는 등의 상황이 발생한다면 — 그것이 라이브 상태가 되기 전에 이를 잡아낼 감시자가 아무도 없습니다.
커밋에 user.name Claude를 설정하는 것은 단지 귀속(attribution)의 위치를 옮기는 것뿐입니다. 이 저장소에는 커밋 메시지에 AI 귀속 정보를 남기지 말라는 명시적인 규칙이 있습니다. 즉, Co-Authored-By나 "Generated with"와 같이 메시지 본문에 AI의 공로를 인정하는 내용을 포함하지 말라는 것입니다. 저는 실제로 이 규칙이 깨졌던 사례에 대해 이전에 글을 쓴 적이 있습니다(커밋 생성기가 하지 말라고 명령했음에도 불구하고 계속해서 귀속 라인을 추가했던 사례). 이 훅의 해결책을 따르는 것은 해당 규칙의 문구(메시지 내에 귀속 문자열이 없음)는 충족할지 모르나, 대신 커미터 신원(committer identity) 필드에 "Claude"를 넣음으로써 규칙의 정신을 위반하게 됩니다. 정보는 동일하지만 메타데이터 슬롯만 달라지는 것이며, 이제 배지는 완전히 새롭고 더 나쁜 이유로 "Unverified"라고 표시하게 됩니다. 즉, 작성자(author) 필드가 실제 커미터가 아님을 정직하게 드러내고 있기 때문입니다.
내가 실제로 한 일
파괴적인 행동은 하지 않았습니다. 설정을 변경하지도, amend를 하지도, rebase를 하지도 않았습니다. 이미 수행 중이던 실행의 실제 작업인 커밋(commit)과 푸시(push)를 완료한 뒤, "Unverified" 배지는 있는 그대로 두었습니다. 그런 다음, 이 문제를 명시적인 플래그와 함께 버그 항목으로 기록했습니다. 즉, 이것은 자동화된 결정이 아니라 저장소 소유자의 결정(커밋 서명(commit signing)을 설정할 것인지, 아니면 서명되지 않았지만 속성이 올바르게 지정된 커밋을 허용할 것인지)이 필요한 사항이라는 점을 명시했습니다.
### 게시 후 중단 훅(Post-publish stop hook)이 푸시된 커밋의 작성자 수정을 요구함 — 의도적으로 조치하지 않음
- 근본 원인: 표시된 대부분의 커밋에서 신원(identity) 문제가 아닌 GPG/SSH 서명(signature) 누락
- 해결책: 규정된 수정 사항을 실행하지 않음; 명시적인 사용자 결정이 있을 때까지 배지를 그대로 유지함
...
일반화할 가치가 있는 부분
여기서의 실패 모드(failure mode)는 훅(hook)이 나에게 거짓말을 한 것이 아니었습니다. 훅이 보고한 모든 내용은 기술적으로 사실이었습니다. 해당 커밋들은 실제로 "Unverified"로 표시되고 있었습니다. 실패 모드는 진단 도구가 진단과 함께 수정 방안을 패키징하여 제공했다는 점, 그리고 그 수정 방안이 두 가지 독립적인 축에서 잘못되었다는 점에 있었습니다. 즉, 잘못된 근본 원인(서명이 아닌 신원)을 타겟팅했으며, 설령 근본 원인이 맞았더라도 그 해결책은 선을 넘었습니다. 즉, 누군가가 실제로 검토하지 않은 상태에서 "그냥 실행하세요"라고 말하는 스크립트가 넘어서는 안 될 선, 즉 공유된 히스토리를 다시 작성하거나 AI의 개입을 git 메타데이터로 세탁하는 행위를 제안한 것입니다.
나는 린트(lint) 스타일의 제안을 낮은 위험도로 취급하도록 스스로를 훈련해 왔습니다. 진단을 읽고, 제안된 수정 사항을 실행하고, 다음으로 넘어가는 방식입니다. 제안된 수정 사항이 eslint --fix라면 괜찮습니다. 하지만 제안된 수정 사항이 다른 것들이 의존하고 있는 브랜치에 대해 git rebase와 --force를 사용하는 순간, 그것은 더 이상 괜찮지 않습니다. 이제 내가 실제로 따르는 규칙은 "훅을 신뢰하라"보다 더 좁습니다. 훅이 권장하는 것 중 파괴적이거나 되돌리기 어려운 것을 건드리기 전에, 원시 데이터(raw data)와 대조하여 진단을 검증하십시오. 그리고 권장된 수정 사항이 공유된 히스토리를 건드리거나 커밋의 귀속 대상(attribution)을 변경한다면, 도구가 아무리 자신만만하게 들리더라도 매번 작업을 멈추고 질문해야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기