AI 코딩 에이전트 가드레일, 훅으로 실수 막는 법

AI 코딩 에이전트가 비밀 파일을 읽거나 기본 브랜치에 커밋하지 못하게 훅으로 가드레일을 거는 법을 정리해요. 지시문과 훅의 역할 나누기, 차단 메시지 쓰기까지 다뤄요.

셀루모2026.10.02 · 12분 읽기
목차

AI 코딩 에이전트에게 일을 맡기면 대부분은 알아서 잘해요. 문제는 가끔 하는 실수가 되돌리기 어려운 종류라는 점이에요. 이 글은 그런 실수를 지시문이 아니라 훅으로 막는 방법을 순서대로 정리해요.

혹시 이런 고민 있으신가요?

  • 에이전트에게 "비밀 파일은 읽지 마" 라고 적어 뒀는데, 정말 지킬지는 매번 불안해요.
  • 사람이 없는 새벽에 에이전트가 도는데, 기본 브랜치에 바로 커밋하면 어쩌나 걱정돼요.
  • 규칙을 늘릴수록 지시문이 길어져서, 에이전트가 앞쪽 규칙을 잊는 것 같아요.

이 글을 읽으면

  • 어떤 규칙은 지시문에 두고 어떤 규칙은 훅으로 강제할지 기준을 세울 수 있어요.
  • 비밀 파일 접근, 기본 브랜치 커밋, 금지 패키지 설치를 막는 훅을 직접 만들 수 있어요.
  • 에이전트가 막혔을 때 엉뚱한 우회로를 찾지 않게 차단 메시지를 쓸 수 있어요.

1단계. 지시문과 훅의 차이를 이해하기

먼저 두 가지 방법을 구분해 볼게요. 지시문은 에이전트에게 읽어 주는 글이에요. 대부분의 코딩 에이전트는 프로젝트 안의 문서를 세션 시작 때 읽고 따르려고 해요. 훅은 다른 이야기예요. 에이전트가 도구를 쓰기 직전이나 직후에 사람이 만든 작은 프로그램이 자동으로 끼어드는 장치예요. 에이전트가 따를 마음이 있든 없든 실행돼요.

식당에 비유하면 이해하기 쉬워요. 지시문은 주방에 붙여 둔 "칼은 도마 위에서만 쓰기" 안내문이에요. 대부분의 직원이 지키지만, 바쁜 날 누군가는 놓칠 수 있어요. 훅은 칼꽂이에 달린 잠금장치예요. 열쇠 없이는 열리지 않으니 지키려는 마음과 상관없이 막혀요.

그렇다고 모든 규칙을 훅으로 거는 건 좋지 않아요. 훅은 기계가 판단할 수 있는 것만 막을 수 있어요. 아래 표가 저희가 쓰는 기준이에요.

규칙의 성격어디에 둘까예시
어기면 되돌리기 어렵고, 기계가 문자열로 판별할 수 있어요훅비밀 파일 읽기, 강제 푸시, 기본 브랜치 커밋
어기면 곤란하지만 상황에 따라 예외가 있어요지시문한 번에 한 가지 일만 하기, 새 의존성은 먼저 묻기
취향이나 문체의 문제예요지시문커밋 메시지 언어, 문서 말투
코드가 맞는지의 문제예요테스트와 CI타입 검사, 린트

에이전트가 도구를 쓰려 하면 실행 직전 훅이 규칙을 검사하고, 통과하면 실행하고 어기면 호출을 차단해 이유를 돌려주는 흐름훅은 도구가 실행되기 직전에 끼어들어요

저희는 기준을 하나로 정했어요. "이 규칙을 어겼을 때 되돌릴 수 없는가, 그리고 문자열 검사로 알아낼 수 있는가" 에 둘 다 "예" 이면 훅이에요. 하나라도 "아니오" 이면 지시문이나 CI 에 둬요.

TIP

규칙을 훅으로 옮겼다면 지시문에서는 규칙의 이유 한 줄만 남기고 지우세요. 같은 내용이 두 곳에 있으면 한쪽만 고쳐져서 서로 어긋나기 쉬워요. 지시문이 짧아지는 것은 덤이에요.

2단계. 막을 것의 목록 정하기

훅을 쓰기 전에 무엇을 막을지부터 정해야 해요. 처음부터 많이 막으면 에이전트가 하는 일마다 걸려서 쓸모없어져요. 저희는 "한 번 일어나면 사람이 수습해야 하는 일" 세 가지로 시작했어요.

  1. 비밀 값이 든 환경 설정 파일을 읽거나 쓰는 것. 에이전트의 대화 기록은 외부 서비스로 가기 때문에, 한 번 읽은 비밀은 이미 밖으로 나간 것으로 봐야 해요.
  2. 기본 브랜치에 직접 커밋하는 것과 강제 푸시. 새벽에 혼자 도는 에이전트가 운영 코드를 바로 바꾸면 아침에 사람이 알아채기 전에 배포될 수 있어요.
  3. 쓰지 않기로 한 패키지를 설치하는 것. 에이전트는 막히면 익숙한 라이브러리를 끌어오는 버릇이 있어서, 팀이 이미 거절한 도구가 슬쩍 들어오곤 해요.

반대로 훅으로 막지 않은 것도 많아요. 코드 스타일, 파일 위치, 테스트 작성 여부는 CI 와 지시문에 맡겼어요. 훅은 적을수록 믿을 수 있어요.

참고

막을 목록은 사고가 났을 때 늘리는 편이 좋아요. 일어나지 않은 사고를 상상해서 훅을 쌓으면 관리할 것만 늘어요. 다만 비밀 값처럼 한 번의 사고가 치명적인 항목은 미리 막으세요.

3단계. 실행 직전에 검사하는 훅 만들기

훅은 에이전트가 쓰려는 도구의 이름과 입력을 JSON 으로 받아요. 검사하는 프로그램은 그 JSON 을 읽고 종료 코드로 답해요. 종료 코드 0 이면 통과이고, 2 이면 호출을 막고 표준 오류에 쓴 글을 에이전트에게 돌려줘요. 어떤 언어로 짜도 되지만, 저희는 설치가 필요 없는 파이썬 표준 라이브러리만 써서 어느 컴퓨터에서든 같게 돌게 했어요.

첫 번째는 비밀 파일 훅이에요. 파일을 읽는 도구뿐 아니라 명령어와 검색 패턴까지 살펴야 해요. 명령어로도, 검색으로도 읽을 수 있으니까요.

PYTHON
import re, sys, json ENV_RE = re.compile(r"(?:^|[\s/\\'\"=:])\.env(?:\.[\w.-]+)?(?=$|[\s'\";|&)])")ALLOWED = (".env.example",)  # 값이 없는 견본은 읽어도 돼요 payload = json.load(sys.stdin)tool_input = payload.get("tool_input") or {}texts = [v for k, v in tool_input.items()         if k in ("file_path", "path", "command", "pattern", "glob") and isinstance(v, str)] for text in texts:    for m in ENV_RE.finditer(text):        token = m.group(0).lstrip(" \t/\\'\"=:")        if token not in ALLOWED:            sys.stderr.write(f"'{token}' 에는 비밀 값이 있어서 읽지 않아요. "                             "키 목록은 견본 파일을 보고, 값은 사용자에게 물어보세요.\n")            sys.exit(2)sys.exit(0)

두 번째는 브랜치 훅이에요. 에이전트가 명령어를 실행하는 도구를 쓸 때, 명령어가 커밋이면 현재 브랜치를 확인해요. 커밋 시점에만 브랜치를 알 수 있으니 그때 검사해야 해요. 강제 푸시는 브랜치와 상관없이 늘 막아요.

PYTHON
import re, subprocess, sys, json COMMIT = re.compile(r"^(?:\S+=\S+\s+)*git(?:\s+-C\s+\S+)?\s+commit\b")PUSH = re.compile(r"^(?:\S+=\S+\s+)*git(?:\s+-C\s+\S+)?\s+push\b")FORCE = re.compile(r"(?:\s--force(?:-with-lease)?\b|\s-f\b|\s\+\S+)") command = (json.load(sys.stdin).get("tool_input") or {}).get("command", "")for part in re.split(r"&&|\|\||;|\||\n", command):    part = part.strip()    if PUSH.match(part) and FORCE.search(part):        sys.stderr.write("강제 푸시는 허용하지 않아요. 명령을 바꾸지 말고 사용자에게 알려 주세요.\n")        sys.exit(2)    if COMMIT.match(part):        branch = subprocess.run(["git", "branch", "--show-current"],                                capture_output=True, text=True).stdout.strip()        if branch in ("main", "master"):            sys.stderr.write(f"현재 브랜치가 '{branch}' 예요. 작업 브랜치를 먼저 만드세요.\n")            sys.exit(2)sys.exit(0)

명령어를 이어 붙여 한 줄에 여러 개를 넣는 경우가 많아서, 위 코드는 먼저 단순 명령으로 쪼개서 하나씩 검사해요. 이 쪼개기를 빼면 추가와 커밋을 한 줄로 이은 명령이 그냥 통과해요.

참고

이 쪼개기는 줄바꿈도 기준으로 삼아서, 여러 줄짜리 입력 안에 커밋 명령처럼 보이는 줄이 있으면 실제 명령이 아니어도 걸려요. 저희도 글 파일을 한 번에 만드는 명령을 돌리다가 이 훅에 막혔어요. 과하게 막는 쪽이 놓치는 쪽보다 낫다고 보고 그대로 두되, 그럴 땐 파일 쓰기 도구로 바꿔서 해결해요.

세 번째는 금지 패키지 훅이에요. 여기서 중요한 건 금지 목록을 훅 안에 박지 않는다는 점이에요. 저희는 "쓰지 않기로 한 도구" 를 팀 규칙 문서의 한 칸에 적어 두고, 훅이 그 칸을 읽어 목록을 만들어요. 그러면 목록을 고치는 곳이 문서 하나뿐이에요. 에이전트가 설치 명령을 실행하려 할 때 이름을 대조하고, 설정 파일을 직접 고쳐서 몰래 넣는 경우를 잡기 위해 파일 수정 직후에도 한 번 더 확인해요.

TIP

훅 입력은 에이전트마다 필드 이름이 조금 달라요. 처음 만들 때는 받은 JSON 을 파일에 그대로 찍어 보고 필드를 확인한 뒤 검사 코드를 쓰세요. 추측해서 쓴 검사는 조용히 아무것도 막지 않아요.

4단계. 훅을 등록하고 막히는지 직접 확인하기

훅 프로그램을 만들었다면 설정 파일에 언제 어떤 도구에서 실행할지 적어야 해요. 실행 시점은 크게 두 가지예요. 도구가 실행되기 직전이면 막을 수 있고, 직후면 결과를 보고 고치게 할 수 있어요. 설정은 대략 이런 모양이에요.

JSON
{  "hooks": {    "PreToolUse": [      { "matcher": "*",    "hooks": [{ "type": "command", "timeout": 10, "command": "python3 guard_secrets.py" }] },      { "matcher": "Bash", "hooks": [{ "type": "command", "timeout": 10, "command": "python3 guard_git.py" }] }    ],    "PostToolUse": [      { "matcher": "Edit|Write", "hooks": [{ "type": "command", "timeout": 60, "command": "python3 format_edit.py" }] }    ]  }}

비밀 파일 훅은 모든 도구에 걸고, 브랜치 훅은 명령어 도구에만 걸어요. 저희는 직후 시점에 코드 서식을 자동으로 맞추는 훅도 하나 두었어요. 이 훅은 도구가 없으면 조용히 건너뛰고 절대 막지 않아요. 막는 훅과 돕는 훅을 섞어 두되, 돕는 훅은 실패해도 작업을 멈추지 않게 하세요.

등록한 뒤에는 반드시 막히는 것을 눈으로 확인하세요. 아래 세 가지를 에이전트에게 시켜 보면 돼요.

  1. 비밀 설정 파일의 내용을 보여 달라고 해요. 차단 메시지가 돌아와야 해요.
  2. 기본 브랜치에서 빈 커밋을 만들라고 해요. 마찬가지로 막혀야 해요.
  3. 견본 파일은 읽어 달라고 해요. 이건 통과해야 해요. 과하게 막는 훅은 에이전트를 쓸모없게 만들어요.

참고

훅은 문자열 검사라서 완벽하지 않아요. 명령어를 변수에 담아 실행하거나 인코딩해서 우회하면 놓칠 수 있어요. 훅은 실수를 막는 장치이지, 악의를 막는 보안 경계가 아니에요. 보안이 필요한 부분은 권한이 없는 계정처럼 시스템 쪽에서 막으세요.

5단계. 막혔을 때의 메시지와 멈춤 조건 설계하기

훅이 막았을 때 에이전트가 무엇을 하느냐가 의외로 중요해요. 메시지가 "금지됨" 한 줄이면 에이전트는 비슷한 다른 방법을 시도해요. 환경 설정 파일을 못 읽게 했더니 같은 파일을 다른 명령어로 읽으려 하는 식이에요. 그래서 메시지에는 세 가지를 담아요.

  • 무엇이 막혔는지. 예: 현재 브랜치가 기본 브랜치예요.
  • 왜 막혔는지. 예: 새벽에 도는 에이전트의 커밋이 바로 배포 대상이 되기 때문이에요.
  • 대신 무엇을 하면 되는지. 예: 작업 브랜치를 만들거나, 값이 필요하면 사용자에게 물어보세요.

강제 푸시 훅의 메시지에는 "명령을 바꿔서 다시 시도하지 말고 사용자에게 알려 주세요" 라는 말을 일부러 넣었어요. 우회를 허락하지 않는다는 뜻을 분명히 한 거예요.

훅에는 에이전트가 일을 끝내지 못하게 하는 용도도 있어요. 여러 작업 파일을 순서대로 처리하는 긴 세션에서는, 에이전트가 중간에 "다 했어요" 하고 끝내 버리는 일이 있어요. 그래서 작업 목록에 미완료 항목이 남아 있으면 종료를 막고 "아직 열린 항목이 몇 개 있어요, 계속하세요" 라고 돌려주는 훅을 두었어요. 이때 종료를 막는 훅이 무한 반복되지 않도록, 이미 한 번 막았다는 표시가 있으면 통과시키는 안전장치가 꼭 필요해요. 이 훅은 작업 세션이 시작될 때 표지 파일을 만들고 끝날 때 지우는 방식으로, 그 세션에서만 켜져요. 평소의 대화까지 막으면 곤란하니까요.

긴 대화는 중간에 요약되면서 앞의 내용이 사라지기도 해요. 그래서 요약이 일어난 직후에 시작되는 훅이 남은 작업 목록과 다음 할 일 세 개를 다시 붙여 줘요. 에이전트가 어디까지 했는지 잊어버려 같은 일을 처음부터 반복하는 것을 막아 줘요.

TIP

차단 메시지는 에이전트가 읽는 글이에요. 사람에게 쓰는 오류 문구처럼 짧게 줄이지 말고, 다음 행동까지 한 문장으로 적어 주세요. 한 줄 늘리는 비용이 우회 시도 한 번 줄이는 효과보다 훨씬 작아요.

자주 묻는 질문

Q1. 훅이 있으면 지시문은 필요 없나요?

아니에요. 훅은 막을 수 있는 것만 막아요. "작게 나눠서 커밋하기" 나 "이유를 설명하기" 같은 건 기계가 판별하기 어려워서 지시문이 맡아요. 훅은 지시문이 놓친 가장 위험한 몇 가지를 받치는 안전망이라고 생각하세요.

Q2. 훅이 잘못 막아서 일이 멈추면 어떻게 하나요?

먼저 차단 메시지가 이유를 말하는지 확인하세요. 검사가 너무 넓다면 정규식을 좁히고, 반대로 허용할 예외가 있다면 허용 목록에 넣어요. 저희 비밀 파일 훅이 견본 파일만 허용 목록에 둔 것이 그런 예예요. 훅을 임시로 끄는 일이 잦다면 규칙 자체가 현실과 맞지 않는다는 신호예요.

Q3. 훅이 에이전트의 속도를 늦추지 않나요?

짧은 파이썬 검사 하나는 보통 눈에 띄지 않을 만큼 빨라요. 다만 서식 맞추기처럼 시간이 걸릴 수 있는 훅은 제한 시간을 따로 넉넉히 주고, 실패하더라도 작업을 막지 않게 하세요.

정리

  • 되돌릴 수 없고 문자열로 판별할 수 있는 규칙만 훅으로 걸고, 나머지는 지시문과 CI 에 맡겨요.
  • 막을 목록은 비밀 파일, 기본 브랜치 커밋과 강제 푸시, 금지 패키지처럼 적게 시작해요.
  • 금지 목록은 훅 안이 아니라 규칙 문서 한 곳에 두고 훅이 읽게 해요.
  • 훅을 등록한 뒤 막혀야 할 것이 막히고 허용할 것이 통과하는지 직접 확인해요.
  • 차단 메시지에는 무엇이, 왜, 대신 무엇을 할지를 담아 우회 시도를 줄여요.

가드레일은 에이전트를 못 믿어서 거는 게 아니라, 사람이 자는 동안에도 안심하고 맡기려고 거는 거예요. 하나만 정해서 오늘 훅으로 옮겨 보세요.

이어서 읽기 좋은 글