
Windows 11에서 AI 에이전트에게 'git push'까지 맡기기 위한 가드레일
요약
AI 에이전트에게 git push와 같은 코드 조작 권한을 부여할 때 발생할 수 있는 위험을 방지하기 위한 'AI 에이전트 하네스(Harness)' 설계 방안을 다룹니다. 샌드박스, Git 설정, GitHub ruleset 등 다층적인 방어 레이어를 통해 에이전트의 오작동과 사고 범위를 제어하는 방법을 제시합니다.
핵심 포인트
- AI 에이전트의 실행 권한과 작업 영역을 정의하는 '하네스' 설계의 중요성
- 프롬프트 지시만으로는 부족하며, 시스템적 가드레일 구축이 필수적임
- 로컬 검사(Git hook)부터 원격 보호(GitHub ruleset)까지 다층 방어 체계 구축
- Windows 11 환경의 개인 개발자를 위한 실질적인 보안 레이어 구성
시작하며: AI 에이전트 하네스(Harness)라는 설계
AI 에이전트에게 코드 편집뿐만 아니라 테스트, 커밋, 작업 브랜치로의 git push, Pull Request 생성까지 맡길 때, 중요한 것은 모델의 성능만이 아닙니다.
에이전트가 어떤 작업 영역에서, 어떤 규칙을 따르며, 어떤 조작을 실행할 수 있는지. 이상 상황이나 판단 불가능한 상황에 직면했을 때 어디에서 처리를 멈출 것인지. 이러한 실행 조건을 미리 환경 측에서 설계해야 합니다.
본고에서는 AI 에이전트의 실행 권한, 작업 영역, 조작 규칙, 로컬 검사, 인증 경로, GitHub 측의 제약을 조합한 실행 기반을 **AI 에이전트 하네스 (AI Agent Harness)**라고 부릅니다.
개인 개발에서는 개발자, GitHub 계정 소유자, AI 에이전트를 실행하는 Windows 사용자가 모두 동일 인물과 연결되어 있는 경우가 적지 않습니다. 따라서 프롬프트에 "main에는 push하지 말 것"이라고 쓰는 것만으로는 불충분합니다.
에이전트가 지시를 오해하거나 예상치 못한 명령을 선택하더라도, 사고 발생 확률과 영향 범위를 낮추는 메커니즘이 필요합니다.
본고에서 구축하는 하네스는 다음 레이어로 구성됩니다.
| 층 | 주로 경감·제어할 수 있는 것 | 방지할 수 없는 것 |
|---|---|---|
| 에이전트의 sandbox · 승인 설정 | 파일, 네트워크, 명령에 대한 액세스 범위 | 허가된 범위 내에서의 판단 실수 |
| Git 설정 | push, pull, fetch 시의 암묵적 동작, 작성자 정보 추측, 자격 증명이 포함된 URL | 명시적으로 실행된 위험한 명령 |
.gitattributes | OS나 실행 환경의 차이로 인한 불필요한 줄 바꿈 차이 | 애플리케이션 고유의 생성 차이 |
AGENTS.md / CLAUDE.md | 작업 절차, 금지 사항, 완료 조건의 공유 | 지시 내용의 강제 |
| 에이전트 고유의 hook | 툴 실행 전후의 검사나 거부 | hook 설정 자체를 변경할 수 있는 주체 |
| Lefthook / Gitleaks | Git 조작 시의 결정적인 로컬 검사, 비밀 정보의 조기 탐지 | --no-verify나 LEFTHOOK=0에 의한 회피 |
git worktree | 여러 태스크의 working tree, index, 커밋되지 않은 변경 사항의 충돌 | 악의적인 조작이나 프로세스의 격리 |
| CI · 외부 체크 (임의) | 빌드, 테스트, Lint 등의 원격 재검증 | 미도입 시, 로컬 검사의 성공을 증명할 수 없음 |
| GitHub ruleset | Pull Request를 경유한 업데이트, force push, 브랜치 삭제 | 보호되지 않은 브랜치에서의 오조작 |
SSH / ssh-agent | 인증 입력 대기, 사용하는 SSH 구현의 불일치 | 동일 Windows 사용자로 동작하는 프로세스 간의 권한 분리 |
중요한 점은 이러한 레이어들이 모두 동일한 강도를 가진 것은 아니라는 점입니다.
AGENTS.md나 로컬 hook은 동일한 사용자 권한을 가진 주체에 의해 변경되거나 회피될 가능성이 있습니다. 반면, 바이패스를 허용하지 않는 GitHub ruleset은 일반적인 로컬 조작으로는 변경할 수 없습니다.
로컬 측에서는 사고를 가능한 한 빨리 탐지하고, 마지막에 GitHub 측에서 보호 브랜치로의 부정 업데이트를 거부하는 구성을 목표로 합니다.
본고의 전제
대상이 되는 것은 Windows 11의 개인 개발 PC에서의 다음과 같은 운용입니다.
- Git for Windows를 사용한다
- 원격(Remote)은 GitHub의
origin을 하나 사용한다 - 기본 브랜치는
main이다 main에서 태스크별 작업 브랜치를 생성한다- AI 에이전트는 작업 브랜치에만 push한다
main으로의 반영은 Pull Request를 통해서만 제한한다- AI 에이전트가 생성하는 Pull Request는 원칙적으로 Draft로 한다
- 태스크마다 전용 worktree를 할당한다
- Lefthook와 Gitleaks를 로컬 검사에 사용한다
- GitHub 접속에는 패스프레이즈가 포함된 Ed25519 키와 Windows의
ssh-agent를 사용한다 - GitHub Actions나 외부 CI는 필수 사항이 아니다
- private 리포지토리에서 GitHub ruleset을 사용하는 경우, 대응하는 GitHub 플랜을 이용한다
여기서 목표로 하는 것은 AI 에이전트를 완전히 격리하는 보안 sandbox가 아닙니다.
동일한 Windows 사용자로서 동작하는 악의적인 프로세스나, Windows 계정 자체가 침해된 상황까지는 방지할 수 없습니다. 대상은 에이전트의 판단 실수, 모호한 Git 설정, 병행 작업의 충돌, 의도하지 않은 push, 검증 부족의 변경과 같은 일상적인 개발 사고입니다.
1. Git보다 먼저, 에이전트 자신의 실행 권한을 좁히기
Git 설정을 정비하기 전에, AI 에이전트가 이용할 수 있는 파일, 네트워크, 명령의 범위를 제한합니다.
Codex에서는 sandbox가 명령으로부터 액세스할 수 있는 파일이나 네트워크의 범위를 제어하고, approval policy(승인 정책)가 sandbox 경계를 넘는 조작을 언제 승인할지를 제어합니다. OpenAI는 무관한 리포지토리(Repository)로 액세스 범위를 넓히기보다, 프로젝트 경계나 worktree를 유지하는 운영을 안내하고 있습니다. (OpenAI Developers)
Claude Code에서는 permission(권한) 설정과 hook(훅)이 이 계층을 담당합니다. CLAUDE.md를 통한 지시와의 역할 분담은 섹션 4에서 다룹니다.
기본 방침은 다음과 같습니다.
- 쓰기 가능 범위를 현재의 프로젝트 또는 worktree로 한정한다
- 네트워크 액세스는 필요한 경우에만 허용한다
- push, 인증 변경, 패키지 추가 등 외부 영향이 따르는 조작은 승인 대상으로 한다
- sandbox나 permission check(권한 확인)를 전면적으로 무효화한 모드를 상용하지 않는다
- 여러 태스크에 넓은 상위 디렉토리를 공유하지 않고, worktree 단위로 작업하게 한다
git worktree는 작업 디렉토리를 분리하지만, 에이전트의 OS 권한을 제한하는 기능은 아닙니다. sandbox나 permission 설정과 worktree는 별개의 레이어로 병용합니다.
2. Git을 모호한 상황에서 멈추기
설정값뿐만 아니라 설정 출처를 확인하기
설정을 추가하기 전에, 현재의 실효값과 설정 출처를 확인합니다.
git --version
git config --show-origin --show-scope --list
Git 설정에는 주로 다음과 같은 스코프(Scope)가 있습니다.
system
global
local
...
git config --global --list만으로는 system 설정이나 리포지토리 고유의 덮어쓰기를 놓칠 수 있습니다.
특정 항목만 조사할 때도 값뿐만 아니라 설정 출처를 표시합니다.
git config --show-origin --show-scope --get user.email
git config --show-origin --show-scope --get fetch.prune
git config --show-origin --show-scope --get pull.ff
...
환경 변수에 의해 Git 설정이 덮어씌워져 있지 않은지도 확인합니다.
Get-ChildItem Env:GIT_* `
-ErrorAction SilentlyContinue |
Sort-Object Name
특히 GIT_SSH_COMMAND가 존재하는 경우, core.sshCommand보다 우선됩니다. (Git)
본고에서 채택하는 Git 설정
| 분류 | 설정 | 채택 값 | 주요 목적 |
|---|---|---|---|
| 작성자 정보 | user.name | <AUTHOR_NAME> | 커밋에 기록할 작성자 이름을 고정함 |
| 작성자 정보 | user.email | <AUTHOR_EMAIL> | 커밋에 기록할 이메일 주소를 고정함 |
| 작성자 정보 | user.useConfigOnly | true | 작성자 정보를 OS로부터 추측하지 않도록 함 |
| 초기화 | init.defaultBranch | main | 신규 리포지토리 (Repository)의 기본 브랜치를 통일함 |
| fetch | fetch.prune | true | 삭제된 remote-tracking ref를 정리함 |
| push | push.default | simple | 현재의 동일한 이름의 브랜치를 안전한 방식으로 push함 |
| push | push.autoSetupRemote | true | 첫 push 시점에 upstream을 설정함 |
| pull | pull.rebase | false | pull 시 암묵적인 rebase를 수행하지 않음 |
| pull | pull.ff | only | fast-forward가 불가능한 pull을 거부함 |
| 충돌 표시 | merge.conflictStyle | zdiff3 | 공통 조상을 포함하는 충돌 표시를 사용함 |
| 충돌 해결 | rerere.enabled | true | 과거의 충돌 해결 내역을 재사용함 |
| 충돌 해결 | rerere.autoUpdate | false | 재사용 결과를 자동으로 stage 하지 않음 |
| rebase | rebase.missingCommitsCheck | error | interactive rebase 시 커밋이 소실되는 것을 방지함 |
| 자격 증명 | transfer.credentialsInUrl | die | URL에 포함된 평문 자격 증명을 거부함 |
| 리포지토리 탐색 | safe.bareRepository | explicit | 암묵적인 bare 리포지토리 조작을 제한함 |
| 줄 바꿈 | core.autocrlf | input | commit 시 LF로 정규화하고, checkout 시에는 변환하지 않음 |
| 줄 바꿈 | core.safecrlf | warn | 되돌릴 수 없는 줄 바꿈 변환에 대해 경고함 |
| SSH | core.sshCommand | 나중에 설정 | Git이 사용하는 SSH 클라이언트를 고정함 |
설정을 적용하기
git config --global user.name "<AUTHOR_NAME>"
git config --global user.email "<AUTHOR_EMAIL>"
git config --global user.useConfigOnly true
...
작성자 정보를 추측하지 않게 하기
user.name과 user.email은 커밋 오브젝트 (Commit Object)의 author와 committer에 기록됩니다. user.name은 GitHub 인증용 사용자 이름이 아니라, 커밋에 표시될 작성자 이름입니다. user.email은 공개 리포지토리의 이력에서 참조할 수 있으므로, GitHub의 noreply 주소와 같이 공개해도 괜찮은 이메일 주소를 사용합니다.
user.useConfigOnly=true를 설정하면, Git은 OS의 사용자 이름이나 호스트 이름으로부터 작성자 정보를 추측하지 않고 설정값만을 사용합니다. 작성자 정보가 부족한 경우, 잘못된 명의로 커밋을 생성하는 대신 설정을 요구하며 중단합니다. (Git)
오래된 remote-tracking ref를 정리하기
git config --global fetch.prune true
fetch.prune=true는 git fetch를 항상 --prune 옵션을 붙인 것처럼 동작하게 합니다. 일반적인 브랜치용 refspec에서는 원격(remote)에서 삭제된 브랜치에 대응하는 다음과 같은 remote-tracking ref를 삭제합니다.
origin/feature/example
다음과 같은 로컬 브랜치는 삭제되지 않습니다.
feature/example
fetch.pruneTags=true
로컬 태그의 삭제를 동반할 가능성이 있기 때문에 무조건적으로 설정하지는 않습니다. 태그를 명시적인 refspec으로 가져오는 mirror 구성 등에서는 일반적인 fetch.prune 설정에서도 태그가 prune 대상이 될 수 있습니다. (Git)
push 대상과 pull 동작을 고정하기
git config --global push.default simple
git config --global push.autoSetupRemote true
git config --global pull.rebase false
...
push.default=simple은 알려진 push 대상(push destination)으로 현재 브랜치를 동일한 이름으로 push합니다. 중앙 집중형 워크플로우(Centralized workflow)에서는 현재 브랜치와 upstream의 이름이 다를 경우 push를 거부합니다.
push.autoSetupRemote=true를 조합하면, upstream이 설정되지 않은 새로운 브랜치에서 첫 번째 git push를 실행했을 때 --set-upstream을 지정한 것과 같이 취급됩니다. Git 공식 문서에서도 동일한 이름의 브랜치를 사용하는 simple한 중앙 집중형 워크플로우가 적용 사례로 언급되어 있습니다. (Git)
pull.ff=only는 fast-forward 이외의 pull을 거부합니다.
fast-forward 가능
→ pull 성공
로컬과 리모트가 분기됨
...
AI 에이전트가 분기 시의 이력 전략(history strategy)을 암묵적으로 선택하게 두지 않고, 명시적인 판단으로 되돌리기 위한 설정입니다. (Git)
여러 리모트 사용 시 push를 더욱 명시화하기
여러 push 대상을 다루는 경우에는 다음과 같은 대체 구성을 검토합니다.
git config --global --unset-all push.autoSetupRemote
git config --global push.default nothing
push.default=nothing에서는 refspec을 생략한 push가 실패합니다.
push 시에는 리모트와 refspec을 명시합니다.
git push origin HEAD
origin만 사용하는 개인 개발이라면 simple과 autoSetupRemote를, 여러 리모트를 다루는 경우에는 nothing을 사용하는 식으로 구분하는 것이 이해하기 쉽습니다. (Git)
충돌(Conflict)을 읽기 쉽게 만들고, 해결 결과를 재사용하기
git config --global merge.conflictStyle zdiff3
git config --global rerere.enabled true
git config --global rerere.autoUpdate false
merge.conflictStyle=zdiff3는 양측의 변경 사항뿐만 아니라 공통 조상(common ancestor)도 표시하면서, 충돌 영역의 전후에서 양측이 일치하는 행을 가능한 범위 내에서 제외합니다. 일반적인 merge 형식보다 변경의 유래를 판단하기가 더 쉬워집니다. (Git)
rerere.enabled=true는 과거에 해결했던 충돌과 동일한 충돌이 발생했을 경우, 그 해결 내용을 재사용합니다.
단, rerere.autoUpdate=false에 의해 재사용된 해결 내용이 무조건적으로 확정되지는 않습니다. 재사용된 내용이 working tree에는 적용되더라도, index에는 자동으로 stage되지 않습니다. 인간 또는 에이전트가 차이점(diff)을 확인한 후에 stage할 수 있습니다. (Git)
interactive rebase에서 커밋 소실 방지하기
git config --global rebase.missingCommitsCheck error
interactive rebase의 todo 목록에서 커밋 행이 실수로 삭제된 경우, Git은 경고만 보낸 뒤 진행하지 않고 정지합니다.
의도적으로 커밋을 제외하고 싶을 때는 행을 삭제하는 대신 drop을 사용합니다. (Git)
평문 자격 증명을 포함하는 URL 거부하기
git config --global transfer.credentialsInUrl die
다음과 같은 URL이 Git 설정에 저장되어 있는 경우, 처리를 실패하게 만듭니다.
단, 현재 탐지 대상은 주로 remote.<name>.url입니다. remote.<name>.pushurl
까지는 탐지하지 않습니다. 완전한 비밀 정보 검사가 아니라, 사고를 줄이기 위한 한 단계입니다. (Git)
암묵적인 bare 리포지토리 조작을 제한하기
git config --global safe.bareRepository explicit
explicit 설정을 사용하면, 최상위 레벨의 --git-dir 또는 GIT_DIR 환경 변수로 명시되지 않은 bare 리포지토리를 Git이 다루지 않게 됩니다.
bare 리포지토리를 일반적인 개발에서 직접 조작하지 않는다면 방어 설정으로서 유효합니다. 반면, bare clone이나 로컬 mirror를 일상적으로 다루는 경우에는 워크플로우에 미치는 영향을 확인해야 합니다. Git 공식 문서에서는 explicit이 Git 3.0의 기본값(default)이 될 예정이라고 명시하고 있습니다. (Git)
Windows의 긴 경로는 필요한 경우에만 활성화하기
깊은 디렉토리 구조를 가진 리포지토리에서 실질적인 문제가 발생하는 경우에는 다음을 검토합니다.
git config --global core.longpaths true
이는 Git for Windows의 C 구현에 의한 명령어로, 긴 경로(long paths)를 처리할 수 있도록 하는 설정입니다.
단, Explorer, IDE, 쉘 스크립트 등 Git 이외의 도구까지 긴 경로를 지원하게 만드는 것은 아닙니다. Git for Windows 측에서도 스크립트로 구현된 Git 명령어는 실패할 가능성이 있다고 설명합니다. 가능하다면 프로젝트를 C:\src\project와 같이 얕은 위치에 두는 것이 더 견고합니다. (Git for Windows)
.gitattributes로 환경 차이를 리포지토리 측에서 제어하기
- Windows와 Linux 양쪽에서 동일한 리포지토리를 다룰 경우, 개행 코드(newline code) 차이만으로도 큰 차이(diff)가 발생할 수 있습니다.
글로벌 설정은 섹션 2에서 적용한 core.autocrlf=input과 core.safecrlf=warn을 전제로 합니다.
core.autocrlf=input은 인덱스(index)에 추가할 때 CRLF를 LF로 변환하지만, 체크아웃(checkout) 시에는 LF를 CRLF로 변환하지 않습니다.
core.safecrlf=warn은 현재의 변환으로 인해 원래 상태로 되돌릴 수 없을 가능성이 있는 경우 경고를 보냅니다. 다만, 경고만 할 뿐 처리는 계속 진행됩니다. (Git)
공유해야 할 규칙은 글로벌 설정이 아니라 리포지토리 내의 .gitattributes에 둡니다.
# 텍스트는 리포지토리 내와 워킹 트리(working tree)에서 원칙적으로 LF
* text=auto eol=lf
# Windows 커맨드 파일만 CRLF
...
필요에 따라 바이너리 파일을 명시합니다.
*.png binary
*.jpg binary
*.zip binary
기존 리포지토리에 도입할 때는 워킹 트리(working tree)가 clean한 상태인지 확인한 후 정규화(normalize)합니다.
git add .gitattributes
git add --renormalize .
git diff --cached --check
...
여기서는 자동으로 커밋하지 않고, 의도한 텍스트 파일만 변경되었는지 확인합니다.
.gitattributes는 리포지토리에 커밋되므로, 각 개발자나 에이전트의 글로벌 설정이 다르더라도 파일 단위의 규칙을 공유할 수 있습니다. (Git)
AGENTS.md와 CLAUDE.md에 작업 계약 작성하기
- Codex는 작업 시작 전에
AGENTS.md를 탐색하여 리포지토리 고유의 지시 사항으로 읽어들입니다. 글로벌 지시 사항과 프로젝트 루트부터 작업 디렉토리까지의 지시 사항이 계층적으로 적용됩니다. (OpenAI Developers)
AGENTS.md에는 긴 이념이 아니라, 실행 결과를 확인할 수 있는 규칙을 작성합니다.
# Repository instructions
## Scope
- Work only in the current branch and worktree.
...
Claude Code에서는 CLAUDE.md를 사용합니다.
공통 규약을 이중으로 관리하지 않기 위해, CLAUDE.md에서 AGENTS.md를 import합니다.
@AGENTS.md
# Claude Code 가이드라인
- 광범위한 아키텍처 변경 전에는 plan mode를 사용합니다.
...
Claude Code는 CLAUDE.md를 강제 설정이 아닌 컨텍스트 (Context)로 취급합니다. 반드시 거부해야 하는 도구 조작에는 PreToolUse와 같은 훅 (Hook)을 사용합니다. (Claude Platform Docs)
지시 파일과 에이전트 훅의 역할 분리
AGENTS.md/CLAUDE.md: 에이전트에게 기대하는 절차를 전달PreToolUse등 제품 고유 훅 (Hook): 도구 호출 직전에 검사 및 거부- Lefthook: Git의 commit·push 경계에서 검사
- GitHub ruleset: 원격 측에서 보호 브랜치(Protected branch)로의 업데이트를 거부
지시 파일에만 의존하지 않고, 가능한 범위 내에서 동일한 규칙을 훅 (Hook)과 GitHub 측에도 배치합니다.
5. Lefthook으로 Git 작업 경계에 검사 배치하기
AGENTS.md는 선언적인 지시입니다. 반면, Lefthook은 commit이나 push와 같은 Git 작업의 경계에서 설정된 명령어를 실행합니다.
Lefthook은 lefthook.yml을 읽어 대응하는 Git 훅 (Hook)을 .git/hooks에 설치합니다. 프로젝트의 패키지 매니저 (Package manager), standalone binary, Windows의 패키지 매니저 등 다양한 설치 방법이 제공됩니다. (Lefthook)
npm을 사용하는 프로젝트에서의 도입 예시
npm install `
--save-dev `
--save-exact `
...
훅 (Hook)을 설치합니다.
npx lefthook install
npx lefthook validate
npx lefthook check-install
check-install은 훅 (Hook)이 설치되어 있고 설정과 동기화되어 있으면 종료 코드 0을, 미도입되었거나 비동기 상태라면 1을 반환합니다. (Lefthook)
npm을 사용하지 않는 프로젝트에서는 Lefthook의 standalone binary 또는 해당 언어/OS에 대응하는 공식 도입 방법을 사용합니다.
Gitleaks 도입하기
Windows에서는 패키지 매니저 또는 Gitleaks 공식 릴리스를 통해 도입합니다.
WinGet을 사용하는 예시입니다.
winget install `
--id Gitleaks.Gitleaks `
--exact
도입이 완료되었는지 확인합니다.
gitleaks version
Gitleaks는 Git 히스토리(History)나 워킹 트리(Working tree)에서 알려진 형식의 비밀 정보를 탐지합니다. 오탐(False positive)을 완전히 배제할 수는 없지만, 토큰이나 비밀키를 커밋하는 사고를 조기에 탐지하는 계층으로 활용할 수 있습니다. (GitHub)
pre-commit과 pre-push 분리하기
모든 검사를 pre-commit에 몰아넣어서는 안 됩니다.
| 경계 | 배치할 검사 |
|---|---|
| pre-commit | staged 차분의 공백, formatter, 변경 파일 대상 고속 lint, Gitleaks |
| ... |
pre-commit은 커밋을 빈번하게 생성하는 사람과 에이전트 모두가 통과하는 단계입니다. 무거운 전체 테스트를 배치하면 훅 (Hook)을 우회하려는 압력이 높아집니다.
formatter, 변경 파일 대상 lint, 비밀 정보 검사 등 빠르고 결정적인 처리를 배치합니다.
타입 검사(Type check)나 통합 테스트(Integration test)는 변경 파일만 전달할 경우 프로젝트 전체와의 불일치를 놓칠 수 있습니다. 전체를 대상으로 실행하고 pre-push에 배치하는 것이 자연스럽습니다.
lefthook.yml 기본 예시
pre-commit:
piped: true
jobs:
...
Gitleaks의 공식 pre-commit 정의에서도 다음과 같은 형식으로 staged 변경 사항을 검사합니다.
gitleaks git --pre-commit --redact --staged
piped: true와 priority
use_stdin: true와 priority를 사용하면, 이전 단계의 검사가 실패한 시점에서 후속 처리를 중단할 수 있습니다. 독립적인 검사를 병렬로 실행할 수도 있지만, 가드레일(guardrail) 용도로는 실패 지점과 실행 순서가 명확한 순차 실행부터 시작하는 것이 다루기 쉬울 것입니다. (Lefthook)
pre-push에서는 실제 push 대상 ref를 검사한다
Git의 pre-push hook에는 표준 입력(standard input)으로 다음 정보가 전달됩니다.
<local ref> <local object id> <remote ref> <remote object id>
이 정보를 해석하면 현재의 브랜치명뿐만 아니라, 실제로 어떤 ref로 push하려고 하는지를 검사할 수 있습니다.
예를 들어, 다음을 거부할 수 있습니다.
refs/heads/main
refs/heads/master
Git의 pre-push hook이 0이 아닌 값으로 종료되면, push 자체도 중단됩니다. (Git)
Lefthook에서 표준 입력을 보조 스크립트로 전달할 경우에는 use_stdin: true를 사용합니다.
단, 여러 작업(job)에 use_stdin: true를 설정하더라도 표준 입력을 받을 수 있는 것은 하나뿐입니다. push 대상 ref 검사와 push 대상 커밋의 Gitleaks 스캔은 하나의 스크립트로 통합하는 것이 더 안전합니다. (Lefthook)
로컬 hook은 보안 경계가 아니다
Git의 pre-commit, commit-msg, pre-push 등은 --no-verify로 회피할 수 있습니다. Lefthook 자체도 LEFTHOOK=0을 통해 무효화할 수 있습니다. (Git)
따라서 Lefthook의 위치 설정은 다음과 같습니다.
- 에이전트와 인간에게 빠르고 결정적인 피드백을 제공하는 로컬 검사 계층
GitHub Actions나 외부 CI를 사용하지 않는 구성에서는 로컬 hook이 성공했다는 것을 GitHub 측에서 재실행하거나 증명할 수 없습니다.
그럼에도 불구하고, 잘못된 커밋이나 잘못된 push가 GitHub에 도달하기 전에 차단할 수 있으므로 도입할 가치는 있습니다.
hook 내에서 LLM을 호출하지 않는다
커밋(commit)이나 push를 할 때마다 LLM API를 호출하는 설계는 피해야 합니다.
hook에는 다음과 같은 성질을 가진 검사를 배치합니다.
- 로컬에서 완결된다
- 동일한 입력에 대해 동일한 결과가 나온다
- 짧은 시간 내에 끝난다
- 실패 이유가 명확하다
- 인간도 재실행할 수 있다
무거운 AI 리뷰가 필요하다면, Pull Request 생성 이후의 별도 공정으로 취급합니다.
6. 태스크마다 worktree를 분리한다
여러 AI 태스크를 동일한 working tree에서 실행하면 다음과 같은 것들이 혼재됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기