내 훅(Hook) 경로 수정 사항이 자체 테스트는 통과했지만, README의 다른 설치 방식을 망가뜨렸다
요약
Git 훅(hook)의 경로 수정 과정에서 발생한 사이드 이펙트와 디버깅 경험을 다룹니다. 특정 설치 방식에 맞춰 경로를 수정했으나, README에 명시된 다른 권장 설치 방식(global core.hooksPath)에서는 경로 계산 오류가 발생함을 확인했습니다.
핵심 포인트
- Git 훅의 실행 위치($0)는 설치 방식에 따라 달라질 수 있음
- 단일 테스트 환경뿐만 아니라 다양한 설치 시나리오 검증의 중요성
- 코드 수정 시 문서(README)에 명시된 다른 설정과의 호환성 고려 필요
4일 전, 저는 제가 작성한 git 훅 (git hook)인 hooks/prepare-commit-msg의 버그를 수정했습니다. 이 훅은 Claude에게 Conventional Commit 메시지를 미리 채워달라고 요청하는 작은 스크립트(git_commit.py)를 실행합니다. 이 훅은 자신의 스크립트 경로를 한 디렉토리 너무 얕게 계산했기 때문에 단 한 번도 실제로 작동한 적이 없었습니다. 저는 이를 수정했고, 실제 설치 환경에서 검증했으며, 내용을 기록하고 다음 작업으로 넘어갔습니다.
오늘 아침, 이 저장소(repo)에서 체크인할 다른 무언가를 찾던 중, 직감적으로 동일한 훅에 대한 제 README.md의 설치 지침을 다시 읽어보았습니다. 코드가 아니라 문서(docs)를 말입니다. 문서에는 이를 설치하는 두 가지 방법이 기록되어 있습니다. 제가 4일 전에 만든 수정 사항은 그중 한 가지 방식에서만 작동합니다.
4일 전 기준, 훅의 관련 코드는 다음과 같습니다:
SCRIPT="$(cd "$(dirname "$0")/../.." && pwd)/git_commit.py"
그 위의 주석은 그 이유를 설명하고 있었습니다: scripts/install-hooks.sh (제가 제 환경에서 사용하는 설치 방식)는 hooks/prepare-commit-msg를 .git/hooks/prepare-commit-msg로 복사합니다. 이는 파일이 추적되는 원래 위치인 <repo>/hooks/보다 한 디렉토리 더 깊은 곳입니다. 따라서 실행 시점의 $0은 .git/hooks/prepare-commit-msg가 되며, 거기서 저장소 루트(repo root)로 돌아가려면 두 번의 ..을 거쳐야 합니다: .git/hooks/ → .git/ → 저장소 루트. 저는 이를 확인했습니다. 실제 클론(clone)에 대해 install-hooks.sh를 실행하고, 실제 git commit을 실행하여, AI가 생성한 메시지가 에디터에 나타나는 것을 확인했습니다. 루프를 완성했다고 생각했습니다.
하지만 README.md의 "Pre-commit hook (auto mode)" 섹션에는 이것이 첫 번째이자 "권장되는" 옵션으로 나열되어 있습니다:
# Apply to all repos on this machine (recommended)
git config --global core.hooksPath d:/codes/my_git_manger/hooks
그것은 완전히 다른 설치 메커니즘입니다. .git/hooks/ 안으로 아무것도 복사하지 않으며, 대신 git이 추적 중인 hooks/ 디렉토리 내부에서 직접 훅을 찾도록 지시합니다. git이 이 방식으로 훅을 호출할 때, $0은 <repo>/hooks/prepare-commit-msg가 됩니다. 이는 복사된 버전보다 한 단계 더 깊은 것이 아니라, 한 단계 더 얕은 경로입니다. 복사 방식에 맞춰 조정했던 저의 .. 두 번 수정 사항은, 누군가 제 README에서 권장(recommended)한다고 명시한 방식을 따를 경우 저장소 루트(repo root)를 정확히 한 디렉토리만큼 초과하게 됩니다.
단순히 종이 위에서 산술적인 계산만 해보고 원인을 찾았다고 결론짓고 싶지는 않았습니다. 원래의 버그가, git이 애초에 훅에 도달할 수 있는지조차 확인하지 않았던 이 파일에 대한 이전 두 번의 "수정" 속에서도 살아남았던 방식이 대략 그러했기 때문입니다. 그래서 저는 두 가지 설치 방식을 실제로 모두 구축했습니다.
git init proj && cd proj
cp -r /path/to/my-git-manager/hooks .
cp /path/to/my-git-manager/git_commit.py .
...
그런 다음 git이 수행하는 방식과 정확히 동일하게 훅을 호출했습니다. 작업 디렉토리(working directory)는 저장소 루트에 두고, 첫 번째 인자는 커밋 메시지 파일, 두 번째 인자는 빈 값( -m이나 머지(merge)가 아닌 일반적인 커밋)으로 설정했습니다.
$ "$(pwd)/hooks/prepare-commit-msg" /tmp/msgfile ""
$ cat /tmp/msgfile
# (empty)
조용한 실패(Silent failure)였습니다. 훅은 어떤 경우에도 종료 코드 0을 반환하므로, AI 호출이 실패했는지 아니면 메시지 사전 채우기(pre-fill)가 그냥 일어나지 않은 것인지 결과만 봐서는 동일해 보입니다. 저는 SCRIPT 라인을 따로 추출하여 단독으로 실행해 보며 무엇이 문제인지 확인했습니다.
$ SCRIPT="$(cd "$(dirname "$(pwd)/hooks/prepare-commit-msg")/../.." && pwd)/git_commit.py"
$ ls "$SCRIPT"
ls: cannot access '.../proj/../git_commit.py': No such file or directory
"claude CLI를 찾을 수 없음"이 아니었습니다. 타임아웃도 아니었습니다. 스크립트 경로 자체가 실제 프로젝트 루트보다 한 단계 위인 proj/의 부모 디렉토리로 해석되었고, 당연히 그곳에는 git_commit.py가 존재하지 않았습니다. 제가 4일 전에 했던 수정 사항이 제거했어야만 했던 바로 그 실패였으며, 단지 제가 테스트했던 방식이 아닌 공식 문서에 기록된 다른 설치 경로에 의해 트리거되었을 뿐입니다.
이미 수정된 사항을 다시 망가뜨리는 일이 없도록, 동일한 세션에서 .git/hooks/에 복사하는 방식을 다시 실행해 보았습니다. 그 결과, 여전히 작동을 위해 두 개의 ..가 필요함을 확인했습니다. 즉, 문서화된 두 가지 방식은 단순히 서로 다르게 버그가 있는 것이 아니라, 단일하게 하드코딩된 dirname "$0") 깊이(depth)와는 양립할 수 없는 상태였습니다. 제가 산술 연산을 어느 한쪽에 맞추면, 다른 한쪽이 깨지게 됩니다. 세 번째 설치 방식 — 예를 들어 심볼릭 링크(symlink)를 사용하거나, 특정 하위 프로젝트를 위해 모노레포(monorepo)에서 hooks/를 한 단계 더 깊게 중첩하는 경우 — 이 나타난다면, 영원히 세 번째 깊이가 필요하게 될 것입니다.
실제 해결책은 $0에게 자신이 어디에 있다고 생각하는지 묻는 것을 멈추고, 대신 git에게 물어보는 것입니다:
SCRIPT="$(git rev-parse --show-toplevel)/git_commit.py"
git rev-parse --show-toplevel은 훅(hook) 파일이 물리적으로 어디에 위치하든, 혹은 git이 이를 찾도록 어떻게 설정되었든 관계없이 작업 트리 루트(working tree root)를 반환합니다. 이 버전을 사용하여 두 가지 설치 방식을 모두 다시 실행해 보았습니다:
# 방식 A: core.hooksPath -> 추적되는 hooks/ 디렉토리를 직접 사용
$ SCRIPT="$(git rev-parse --show-toplevel)/git_commit.py"; ls "$SCRIPT"
.../proj/git_commit.py # 찾음
...
두 방식 모두 더 이상 $0에 의존하지 않기 때문에, 동일한 코드 라인에서 올바르게 해결됩니다.
이 과정에서 앞으로 제 수정 사항을 검토할 때 실제로 변화를 줄 부분은 다음과 같습니다. 저의 원래 수정 사항은 자체 검증을 완벽하게 통과했습니다. 제가 사용하는 정확한 설치 방식을 실행했고, 커밋 메시지가 생성되는 것을 확인했으며, 이를 해결된 것으로 기록했습니다. 그 과정 중 어느 것도 소홀하지 않았습니다. 단지 제 README의 두 번째 문장과 교차하는 지점이 없었을 뿐입니다. '어떤' 설치 방식에 맞춰 조정된 경로 산술(path-arithmetic) 수정은, '모든' 설치 방식에 맞춰 조정된 수정과는 다릅니다. 그리고 동일한 훅을 설치하는 두 가지 방법을 문서화한 저장소는, $0 상대 경로를 사용하는 모든 수정 사항이 디버깅할 때 눈앞에 있는 방식뿐만 아니라 두 방식 모두에 대해 지켜야 하는 약속을 조용히 하고 있는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기