차단(Block), 조종(Steer), 재작성(Rewrite): 코딩 에이전트 가드 훅(guard hook)은 하나의 응답이 아닌 세 가지
요약
코딩 에이전트의 보안과 효율성을 높이기 위한 런타임 훅(runtime hook)의 세 가지 방식인 차단(Block), 조종(Steer), 재작성(Rewrite)을 소개합니다. 단순 거부를 넘어 명령을 자동으로 수정하여 실행하는 재작성 방식의 효율성을 강조합니다.
핵심 포인트
- 차단(Block): 에러를 반환하여 에이전트가 재시도하게 만드는 가장 기본적인 방식
- 조종(Steer): 에이전트의 행동을 특정 방향으로 유도하는 방식
- 재작성(Rewrite): 명령을 허용된 형태로 자동 변형하여 재시도 없이 즉시 실행하는 가장 고도화된 방식
- PreToolUse 훅을 통해 셸 명령 실행 전 보안 및 일관성 유지 가능
코딩 에이전트(coding agent)를 보호하는 런타임 훅(runtime hook)은 하나의 응답이 아닌 세 가지 응답을 가집니다. 명령을 차단(Blocking)하는 것은 그중 가장 약한 방식입니다.
제 코딩 에이전트는 몇 가지 읽기 전용 헬퍼 스크립트(helper scripts)를 지속적으로 실행합니다. 이는 저장소(repository)가 여전히 자체적으로 일관성을 유지하고 있는지 알려주는 체크(checks) 작업입니다. 스크립트를 저장소 상대 경로(repo-relative name)로 실행하면, 하네스(harness)가 제 권한 허용 목록(permission allowlist)과 대조하여 통과시킵니다. 하지만 정확히 동일한 스크립트를 절대 경로(absolute path)로 실행하면 허용 목록에서 누락되어, 하네스가 중단되고 저에게 승인을 요청합니다. 동일한 스크립트, 동일한 효과임에도 불구하고, 에이전트가 긴 이름을 사용할 때마다 제가 매번 수동으로 승인해야 하는 프롬프트(prompt)가 발생합니다.
문서화된 해결책은 절대 경로 형태를 차단(blocks)하는 훅(hook)입니다. 즉, 에러와 함께 종료(exit)하고, "상대 경로를 사용하세요"라는 피드백을 전달하여 에이전트가 다시 시도하게 만드는 것입니다. 이 방식은 작동합니다. 하지만 매번 전체 라운드 트립(round trip) 비용이 발생합니다. 에이전트가 명령을 내리고, 거부당하고, 수정 사항을 다시 읽고, 다시 타이핑하고, 다시 실행하는 과정이 필요합니다.
제 가드(guard)는 거부(refusal)가 할 수 없는 일을 수행합니다. 절대 경로 호출을 매칭하여, 이를 허용 목록에 있는 상대 경로 형태로 재작성(rewrites)한 뒤, _그것_을 실행합니다. 단 한 번의 패스(pass)로 프롬프트도, 재시도도 필요 없습니다. 에이전트는 저를 방해했을 명령을 내렸지만, 마치 처음부터 올바른 것을 타이핑한 것처럼 깔끔한 결과를 돌려받습니다. 전체 메커니즘은 성공적인 종료 시 반환되는 하나의 JSON 객체입니다:
{"hookSpecificOutput": {
"permissionDecision": "allow",
"updatedInput": {"command": "bash scripts/check-foo.sh"}}}
permissionDecision: "allow"와 updatedInput은 하네스(harness)에 두 가지를 알려줍니다. 질문하지 말 것, 그리고 에이전트가 입력한 것 대신 _이것_을 실행할 것. 명령은 실행 과정 중에 변형(mutated in flight)됩니다. 에이전트는 아무런 충돌도 인지하지 못합니다.
그것은 대부분의 가드 훅(guard hooks)이 결코 올라가지 못하는 사다리의 맨 윗부분입니다. 에이전트가 발행하는 모든 셸 명령(shell command) 이전에 실행되는 기능인 PreToolUse 훅은 세 가지의 뚜렷한 응답 방식을 사용할 수 있습니다: 차단(block), 조종(steer), 그리고 **재작성(rewrite)**입니다. 여러분이 실제로 접하게 될 거의 모든 사례는 가장 낮은 단계만을 사용합니다. 이 에세이는 나머지 두 가지 방식, 그리고 왜 모두가 가장 먼저 선택하는 '차단(blocking) 거부'가 이 세트에서 가장 약한 도구인지에 대해 다룹니다.
세 가지 단계
이 세 가지는 모두 단일 훅 스크립트 내에 존재하며, 한 번에 하나의 규칙씩 추가되어 200줄 미만의 셸(shell) 코드로 구성됩니다.
**차단(Block)**은 종료 코드(exit code) 2입니다. 하네스(harness)는 훅의 stderr를 에러로서 모델에 다시 전달하고 도구 호출(tool call)을 중단합니다. 그러면 에이전트는 다시 시도해야 합니다. 제 훅에서 진정한 충돌 방지 장치(crash-guards)가 작동하는 곳이 바로 여기입니다. 컴파일이 실행 중일 때 캐시 삭제 명령을 거부하십시오. 진행 중인 빌드를 손상시키기 때문입니다. 이미 컨테이너 빌드가 진행 중일 때 두 번째 컨테이너 빌드를 거부하십시오. 동시에 두 개를 실행하면 머신이 다운되기 때문입니다. 작업 중간에 파괴적인 리셋(destructive reset)을 거부하십시오. 이 모든 것은 정당하며, 모두 동일한 단계에 해당합니다: 에이전트를 멈추고, 문장을 돌려주어, 다시 발행하게 만드는 것입니다. 모든 훅 튜토리얼에서 재현되는 전형적인 "위험한 명령 차단" 훅은 정확히 이 단계이며, 오직 이 단계만을 다룹니다.
**조종(Steer)**은 종료 코드 0과 additionalContext라고 불리는 필드를 사용하는 방식입니다. 명령은 거부되지 않습니다. 대신 훅은 에이전트가 다음 행동을 계획하기 전에 읽을 수 있는 노트를 반환합니다. 제 가드(guard)는 거부(veto)하기보다는 넛지(nudge, 슬쩍 유도하기)를 하기 위해 이를 사용합니다. 빌드 전에 디스크 용량이 부족해지면, 재생 가능한 캐시를 삭제하고, 여전히 공간이 부족하면 에이전트에게 이를 알립니다. 에이전트가 무거운 통합 테스트 스위트(integration suite)를 직접 실행하려고 하면, 환경을 먼저 준비하는 래퍼(wrapper)를 사용하도록 권고합니다. Birgitta Böckeler은 이러한 방식의 피드포워드(fed-forward) 가이드를 "좋은 종류의 프롬프트 인젝션(prompt injection)"이라고 부르는데, 이것이 이 단계에 대한 올바른 프레임입니다. 즉, 현재의 생성을 중단하는 것이 아니라 다음 세대를 형성하는 것입니다.
**Rewrite (재작성)**가 주도합니다: exit 0, permissionDecision: "allow", 그리고 updatedInput을 반환합니다. 훅(hook)은 수정된 명령어를 대체하여 실행합니다. 메시지도, 재시도(retry)도, 프롬프트(prompt)도 필요 없습니다.
왜 차단(Block)이 가장 취약한 단계인가
각 단계가 에이전트에게 어떤 비용을 발생시키는지 살펴보십시오. 차단(Block)은 잘못된 명령을 중단시키지만 모든 마찰(friction)을 그대로 남겨둡니다. 즉, 에이전트는 여전히 거절을 인지하고, 수정을 파싱(parse)하며, 다시 발행(reissue)해야 합니다. 조종(Steer)은 에이전트가 다음 단계를 계획할 때 이미 수정 사항을 손에 쥐고 있기 때문에 인지 비용을 삭제합니다. 재작성(Rewrite)은 수정할 것이 아예 없었기 때문에—올바른 것이 이미 실행되었기 때문에—재시도(retry) 비용을 완전히 제거합니다.
따라서 당신이 선택하는 단계는 안전(safety)의 문제인 만큼이나 마찰(friction)에 대한 결정이기도 합니다. 명령어가 진정으로 위험하고 안전한 버전이 없는 경우라면 차단(Block)이 옳습니다. 예를 들어, 두 개의 동시 빌드(concurrent builds)가 진행 중인 깨끗한 중간 컴파일(mid-compile) 상태라면, 명령어가 실행되지 않는 것만이 유일하게 수용 가능한 결과입니다. 하지만 가드(guard)가 실제로 가로채는 것의 상당 부분은 전혀 위험하지 않습니다. 그것은 의도는 맞지만 형식(form)이 틀린 명령어입니다. 잘못된 경로를 가진 올바른 스크립트, 래퍼(wrapper)가 없는 올바른 스위트(suite) 같은 것들입니다. 이러한 부류의 경우, 거절은 불필요한 작업(busywork)일 뿐입니다. 당신은 이미 올바른 형식을 알고 있습니다. 훅(hook)은 그저 그것을 실행하기만 하면 됩니다.
거부 훅(deny-hook)을 사용하는 사람들은 모든 가로채기를 위험한 사례로 취급하며 시작합니다. 왜냐นั้น 그들에게는 가장 낮은 단계(bottom rung)가 유일한 수단이기 때문입니다.
커밋 타임 체크(commit-time checks)가 도달할 수 없는 계층
왜 이것이 커밋 전에 실행하는 check-* 스크립트 중 하나가 아니라, 런타임 훅(runtime hook)에 존재해야 할까요? 그 이유는 두 가지가 서로 다른 것을 보기 때문입니다. 커밋 타임 체크(commit-time check)는 기록될 예정인 파일들을 읽습니다. 실행된 후 사라지는 명령—잘못된 경로, 다른 것과 충돌할 빌드 등—은 git 훅이 검사할 수 있는 커밋 단계에 결코 도달하지 않습니다. 가드(guard)의 헤더 자체에도 그 경계가 명확히 명시되어 있습니다: 이것들은 "git 훅이 포착할 수 없는 런타임 동작 규칙(runtime-behaviour rules)(이들은 결코 커밋에 도달하지 않음)"입니다. 이는 제가 이전에 작성했던 것과 동일한 체크 그래프, 즉 서로를 고정하는 아티팩트(artifact)의 망이며, 여기에 한 가지 층이 더 추가된 것입니다: 커밋(commit) 전이 아니라 실행(execution) 전에 작동하는 체크입니다.
한 가지 설계 규칙이 그 계층을 유지합니다: 가드는 실패 시 개방(fail open)됩니다. 어떤 파싱 문제나 예상치 못한 입력이 발생하더라도, 가드는 종료 코드 0을 반환하며 길을 비켜줍니다. 자신이 보호하는 도구를 멈추게 할 수 있는 가드는 가드가 없는 것보다 더 나쁩니다. 왜냐하면 실패 모드가 에이전트가 작업 중간에 끼어버려 앞으로 나아갈 방법이 없는 상태가 되기 때문입니다. 가드가 제공하는 안전함이 멈춰버린 하네스(harness)의 꼬리 위험(tail risk)을 감수할 만큼 가치 있지는 않으므로, 가드는 결코 그런 상황을 만들지 않도록 구축되었습니다.
사다리 확장하기
새로운 단계(rung)는 어디에서 올까요? 기록된 마찰 루프(friction loop)에서 옵니다. 승인된(approved) 권한 프롬프트는 에이전트에게 보이지 않습니다. 에이전트가 돌려받는 도구 결과는 자동으로 허용된 결과와 동일하기 때문입니다. 제가 승인한 프롬프트는 에이전트가 조치할 수 있는 흔적을 남기지 않으므로, 신호는 에이전트가 보는 곳이 아니라 하네스가 보는 곳에서 포착되어야 합니다. 처리되지 않고 가드를 통과하는 모든 명령은 로컬 로그에 추가됩니다. 작업 세션이 끝나면 스캔을 통해 실제로 저를 자극했던 것들을 패턴별로 그룹화하여 순위를 매기고, 저는 반복되는 각 항목을 세 가지 종류 중 하나로 해결합니다: 새로운 허용 목록(allowlist) 항목, 새로운 가드 규칙, 또는 저 자신의 습관 변화입니다. 중간 단계의 해결책이 바로 사다리의 단계를 확장하는 방식입니다. 두 번 마주친 마찰은 세 번째에는 조종(steer)이나 재작성(rewrite)이 됩니다.
이 전체 장치를 단순히 "권한 프롬프트(permission prompts)와 싸우는 것"으로 분류하고 싶은 유혹이 들겠지만, 이는 잘못된 프레임 설정입니다. 동일한 훅(hook)은 마찰을 아래로 밀어내기도 하고(올바른 의도의 명령을 재작성(rewrite)하거나, 읽기 전용 검색 파이프라인을 자동 허용하는 등), 마찰을 위로 밀어내기도 합니다(깔끔한 중간 빌드나 두 번째 동시 빌드를 거부하는 등). 이것은 프롬프트에 대항하는 캠페인이 아니라, 다양한 응답 범위를 가진 하나의 조종(steering) 레이어입니다. 이 사다리는 셸 명령에만 국한되지 않습니다. 차단(block) 단계는 파일 쓰기 경계(file-write boundary)에도 존재하며, 여기서 형제 훅(sibling hook)은 은퇴했거나 관리되지 않는 표면을 재생성하려는 쓰기 작업을 거부합니다. 동일한 단계이지만 도구가 다를 뿐입니다.
무엇이 나의 것이고 무엇이 아닌가
이 메커니즘 중 제 것이라고 할 만한 것은 거의 없습니다. 훅 참조(hooks reference) 문서에 모든 것이 기록되어 있습니다: exit 2를 통해 stderr를 모델에 다시 전달하는 것, 노트를 주입하기 위한 additionalContext, 그리고 "도구가 실행되기 전에 도구의 인자를 교체하는" updatedInput을 포함한 permissionDecision 등이 그것입니다. deny-dangerous-commands 훅은 어디에서나 재현되는 전형적인 작동 예시입니다. 좋은 가드(guard)는 단순히 실수를 멈추는 것이 아니라 실수를 교정(correct)해야 한다는 근본적인 직관조차도 수십 년 된 것입니다. 이것은 도요타 생산 시스템(Toyota Production System)의 실수 방지 원칙인 포카요케(poka-yoke)와 같습니다. 가장 좋은 고정 장치는 알람을 울리고 인간을 기다리는 대신, 잘못된 동작을 조용히 바로잡거나 불가능하게 만듭니다.
제가 추가하고 있는 것은 프레임워크(framing)입니다. 문서화된 이 세 가지 응답은 하나의 사다리를 형성하며, 서로 마찰을 교환합니다. 그리고 거의 모든 사람이 기본값으로 사용하는 단계는 이 세 가지 중 가장 약한 단계입니다. 특히 재작성(rewrite) 단계는 문서화되어 있음에도 불구하고, 제가 읽은 가이드들에서는 그 본질적인 용도, 즉 마찰을 단속하는 대신 마찰을 제거하는 방법으로서 거의 가르쳐지지 않고 있습니다.
한계
재작성 (Rewrite)은 대체(substitution)가 동작을 보존할 때만 안전합니다. 저의 가드(guard)는 스크립트 경로를 동일한 스크립트의 다른 철자로 재작성합니다. 즉, 동일한 작업 디렉토리, 동일한 효과를 가지며 권한 승인 절차(permission round trip)를 거치지 않습니다. 명령어를 다른 동작을 수행하는 무언가로 재작성하는 것은 편의성으로 포장된 함정이 될 것입니다. 왜냐하면 에이전트가 자신이 입력한 내용이 실제로 실행된 내용이라는 신뢰를 잃게 되기 때문입니다. 따라서 재작성 단계(rewrite rung)는 의도적으로 좁게 설정되었습니다. 이는 알려진 읽기 전용 헬퍼(read-only helpers) 세트에 대해서만 실행되며, 해당 목록에 없는 스크립트는 수정 사항과 함께 조종(steered)될 뿐, 결코 조용히 변경되지 않습니다.
또한 이 계층(ladder)은 전적으로 명령 경계(command boundary)에서 작동합니다. 이는 실행되는 내용을 형성할 뿐, 그 결과가 유익했는지에 대해서는 아무것도 말하지 않습니다. 재작성된 명령어라 할지라도 자체적인 결함으로 인해 실패할 수 있으며, 명령어를 통과시킨 가드가 그 명령어가 수행한 작업의 품질을 검증한 것은 아닙니다. 그것은 별도의 축이며 고유한 규율이 필요한 영역입니다. 저는 다른 곳에서 주장했듯이, 에이전트가 내놓은 성공적인 결과는 확인해야 할 주장(claim)이지, 신뢰해야 할 사실(fact)이 아닙니다.
그리고 이 모든 것은 하네스 배관(harness plumbing) 작업입니다. 이는 특정 벤더의 훅 계약(hook contract)을 기반으로 작동하며, 그 계약은 계속 변합니다. 필드가 추가되고, 동작이 바뀌며, 지난달에 작동했던 예시를 다시 검토해야 할 수도 있습니다. 제가 언급한 파일 쓰기 가드(file-write guard)가 존재하는 이유는 정확히 플랫폼 기능이 제 발밑에서 변했고, 그 변화를 지탱할 단단한 바닥이 필요했기 때문입니다. 이것은 이식 가능한 거버넌스(portable governance)가 아닙니다. 제가 매일 사용하는 특정 도구를 위한 로컬 인체공학(local ergonomics)이며, 그 규모에서는 한 세션 동안 몇 번이고 제 몫을 다합니다.
코딩 에이전트를 지시함으로써 도메인 밀집형 시스템(domain-dense systems)을 구축하는 것이 제가 시간을 보내는 곳이며, 이러한 종류의 인체공학은 그것이 작동하게 만드는 작은 부분입니다. 만약 당신도 같은 문제를 겪고 있다면, LinkedIn을 통해 연락해 주세요.
이 글은 해당 플랫폼이 구축된 것과 동일한 방식으로 AI 에이전트를 지시하며 작성되었습니다. 편집과 판단은 저의 몫입니다.
References
참고 자료 (References)
- "Hooks reference", Claude Code 문서 (2026년 7월 3일 접속).
- Birgitta Böckeler, "Maintainability Sensors for Coding Agents", martinfowler.com, 2026년 5월 19일 (2026년 7월 3일 접속).
- "Poka-yoke", Wikipedia (2026년 7월 3일 접속).
- Vasyl Tretiakov, "Verify the Work", vasyltretiakov.dev, 2026년 6월 24일.
- Vasyl Tretiakov, "Couple Both Ways", vasyltretiakov.dev, 2026년 6월 9일.
vasyltretiakov.dev에서 게시됨.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기