커밋하기 전에 한 번 더 막아주는 AI — 서브에이전트로 커밋 검증 자동화하기
들어가며: 사람이 매번 기억할 수는 없다
JobRadar 프로젝트에는 절대 어기면 안 되는 규칙이 몇 개 있다. 그중 제일 무서운 건 인증 관련 규칙이다.
- 이메일이나 user ID를 코드에 하드코딩 금지 (
'hyunseok.yu1@gmail.com'같은 거) - 모든 DB 조회·수정에
.eq('user_id', profile.id)필터 필수 - 특히
supabaseAdmin(service role)은 RLS(행 수준 보안)를 우회하니, 코드에서 직접 user_id로 걸러주지 않으면 남의 데이터까지 건드릴 수 있다
이런 규칙은 한 번 어기면 보안 사고로 직결된다. 그런데 사람은 급하게 커밋할 때 꼭 깜빡한다. "이번 한 번쯤이야" 하고 넘긴 게 프로덕션까지 가는 게 현실이다.
그래서 이번에 커밋 직전에 변경사항을 자동으로 검사하는 AI 서브에이전트를 만들었다. Claude Code의 서브에이전트 기능을 쓰면, "커밋해줘"라고 말할 때마다 자동으로 끼어들어서 규칙 위반을 잡아주는 검문소를 세울 수 있다. 이 글은 그 pre-commit-validator를 만든 과정과, 설계하면서 신경 쓴 점들이다.
서브에이전트가 뭔데?
Claude Code에서 서브에이전트(subagent) 는 특정 작업에 특화된 별도의 AI 일꾼이다. 메인 대화와 분리된 자기만의 컨텍스트에서 일하고, "이런 상황에서 나를 불러줘"라는 설명(description)을 달아두면 메인 흐름이 알아서 호출해준다.
쉽게 말해 전문 담당자를 한 명 고용하는 것과 비슷하다. 나(메인)는 코드를 짜는 데 집중하고, 커밋 검증 같은 반복적이고 꼼꼼함이 필요한 일은 전담 검증관에게 맡기는 식이다.
서브에이전트는 보통 .claude/agents/ 폴더에 마크다운 파일 하나로 정의한다. 파일 위쪽 frontmatter에 메타데이터, 아래 본문에 "너는 누구이고 무슨 일을 하는지"를 적는다.
Step 1. 언제 불려 나올지 정하기 (description)
서브에이전트에서 가장 중요한 건 사실 description이다. 이게 "어떤 상황에서 이 에이전트가 자동으로 호출될지"를 결정하기 때문이다. 아무리 잘 만들어도 안 불려 나오면 의미가 없다.
---
name: "pre-commit-validator"
description: "Use this agent when the user requests a commit, deployment,
or push (e.g. '커밋', '배포', '올려줘') and you need to verify changes
before they go live. ..."
model: sonnet
color: cyan
memory: project
---
포인트가 몇 개 있다.
| 필드 | 의미 |
|---|---|
description | 호출 트리거. "커밋/배포/푸시 요청 시"라고 명확히. 한국어 트리거 단어('커밋', '배포', '올려줘')까지 박아둠 |
model: sonnet | 검증은 빠르고 정확해야 하니 가벼운 모델로 |
memory: project | 프로젝트 단위 메모리 사용 — 반복되는 위반 패턴을 학습해 쌓아둠 |
description에는 <example> 블록도 여러 개 넣었다. "사용자가 '이거 커밋해줘'라고 하면 → 이 에이전트를 호출한다" 같은 구체적인 시나리오를 예시로 주면, 호출 판단이 훨씬 정확해진다.
팁: description은 "이 에이전트가 뭘 하는지"가 아니라 "언제 불러야 하는지" 중심으로 써야 한다. 능력 설명이 아니라 호출 조건 설명이다.
Step 2. 무엇을 검사할지 — 체크리스트 등급화
본문에는 검증 규칙을 심각도별로 등급을 나눠서 정리했다. 모든 위반을 똑같이 취급하면 진짜 위험한 걸 놓치기 때문이다.
### 🔴 CRITICAL — 절대 통과 불가 (하나라도 있으면 FAIL)
- 하드코딩된 이메일/ID 금지
- 환경변수/시크릿 노출 금지 (.env, sk-, SERVICE_ROLE 등)
- 인증 패턴 누락 (getAuthUserEmail → getOrCreateProfile)
- user_id 필터 누락 (특히 supabaseAdmin 사용 시 필수)
### 🟡 IMPORTANT — 통과는 되나 경고
- import 경계 (Supabase는 lib/supabase.ts에서만)
- API Route 위치 (src/app/api/ 하위만)
- 'use client' 남용, TypeScript strict 위반
### 🟢 ADVISORY — 품질 권고
- 남은 console.log, 디버그 코드, TODO
이렇게 3단계(🔴/🟡/🟢)로 나누니, 검증 결과가 명확해진다. 🔴이 하나라도 있으면 무조건 FAIL, 나머지는 경고로 알려주되 진행은 막지 않는다. 보안 사고로 이어질 수 있는 것과 단순 코드 청결 문제를 섞지 않는 게 핵심이었다.
Step 3. 검증만 하고, 커밋은 직접 하지 않게
설계하면서 중요하게 정한 원칙이 하나 있다. 이 에이전트는 검증만 하고, 실제 커밋/푸시는 직접 하지 않는다.
You do NOT perform the commit/push yourself; you produce a clear
PASS/FAIL verdict with actionable findings so the main flow can
proceed or stop.
왜 이렇게 했냐면, 역할을 섞으면 위험하기 때문이다. "검증하는 사람"과 "실행하는 사람"을 분리해두면:
- 검증관은 오직 판정에만 집중한다 (PASS/FAIL + 근거)
- 실제 git 작업은 메인 흐름이, 사용자 확인을 거쳐 한다
- 검증을 통과 못 하면 거기서 멈춘다
이건 현실의 코드 리뷰와 똑같다. 리뷰어가 "LGTM(좋아 보임)" 또는 "이거 고쳐주세요"라고 판정만 하고, 머지 버튼은 별도로 누르는 것과 같은 구조다.
Step 4. 검증 범위는 "지금 바뀐 것"만
또 하나 명확히 한 건 검사 범위다. 전체 코드베이스를 매번 훑으면 느리고, 관련 없는 기존 코드까지 트집 잡게 된다. 그래서 "지금 커밋하려는 변경분만" 보도록 했다.
1. 변경사항 수집: git status, git diff, git diff --staged 로
"지금 커밋되려는 것"만 식별. 이 변경 라인/파일에만 집중.
2. 규칙별 검사: 각 변경 파일을 체크리스트에 대조.
3. 판정: ✅ PASS / ❌ FAIL + 우선순위별 이슈 목록.
그리고 출력 형식도 한국어로 고정된 템플릿을 줬다. 매번 같은 모양으로 결과가 나오니 읽기 편하고, "어디 파일 몇 번째 줄이 왜 문제인지 + 어떻게 고치는지"를 항상 같이 적게 했다. 문제만 지적하고 해법이 없으면 검증관으로서 반쪽짜리니까.
Step 5. 메모리로 똑똑해지게
마지막으로, 이 에이전트에는 프로젝트 메모리를 붙였다. 검증을 반복하면서 발견한 패턴을 쌓아두게 한 것이다.
**Update your agent memory** as you discover recurring violation
patterns, project-specific conventions, and validation insights.
기록할 만한 것들:
- 반복되는 위반 (특정 파일이 자꾸 identity를 하드코딩한다든가)
- 검증 중 확인된 안전한 패턴 (승인된 헬퍼 사용법 등)
- service-role을 정당하게 쓰는 파일과 그 필수 필터
이렇게 하면 대화가 끝나도 지식이 남는다. "아, 이 파일은 전에도 같은 실수를 했었지" 같은 맥락이 누적되면서, 검증관이 시간이 지날수록 우리 프로젝트에 더 특화된다. 더구나 memory: project라 버전 관리로 팀과 공유되니, 한 명이 발견한 패턴을 모두가 누리게 된다.
트러블슈팅: 만들 때 빠지기 쉬운 함정
서브에이전트를 처음 만들 때 흔히 겪는 것들.
| 증상 | 원인 | 해결 |
|---|---|---|
| 에이전트가 자동 호출이 안 됨 | description이 "능력 설명"에 치우침 | "언제 불러야 하는지" + 트리거 단어/예시를 명확히 |
| 사소한 것까지 다 막아서 거슬림 | 심각도 구분 없음 | 🔴/🟡/🟢 등급으로 나눠 🔴만 차단 |
| 검사가 느리고 엉뚱함 | 전체 코드베이스를 매번 검사 | git diff 변경분으로 범위 한정 |
| 결과가 매번 제각각 | 출력 형식 미지정 | 고정 템플릿(파일:라인 + 수정법) 강제 |
정리: 규칙을 "사람의 기억"에서 "시스템"으로
이번 작업의 핵심을 한 줄로 줄이면, "매번 사람이 기억해야 했던 규칙을, 자동으로 검사하는 시스템으로 옮긴 것" 이다.
pre-commit-validator의 설계 원칙을 정리하면:
- 호출 조건 명확히 — description에 트리거 단어 + 예시로 "언제 부를지" 못 박기
- 심각도 등급화 — 🔴(차단)/🟡(경고)/🟢(권고)로 진짜 위험만 막기
- 역할 분리 — 검증만 하고 커밋은 안 함 (리뷰어와 실행자 분리)
- 범위 한정 — 전체가 아니라
git diff변경분만 - 메모리 누적 — 반복 위반 패턴을 학습해 팀과 공유
개발을 다시 시작하면서 느끼는 건, AI를 "코드를 대신 짜주는 도구"로만 보면 절반만 쓰는 거라는 점이다. 오히려 내가 놓치기 쉬운 걸 대신 지켜보게 하는 안전장치로 쓸 때 진가가 나온다. 사람은 실수하고, 급하면 규칙을 잊는다. 그 빈틈을 시스템으로 메우는 것 — 이게 서브에이전트를 만들면서 가장 크게 와닿은 부분이다.
다음엔 이 검증관이 실제 커밋 흐름에서 어떻게 동작하는지, 실전 PASS/FAIL 사례를 모아 후속편으로 정리해볼 생각이다.
backtodev
40대 PM, 다시 개발자로 돌아갑니다. 실패하고 배우며 성장하는 기록.