AI 코딩 어시스턴트를 위한 가드레일 구축: Claude Code용 PreToolUse Hook 시스템

발행: (2026년 1월 19일 오전 07:09 GMT+9)
13 min read
원문: Dev.to

Source: Dev.to

죄송합니다만, 번역하려는 전체 텍스트를 제공해 주시면 해당 내용을 한국어로 번역해 드릴 수 있습니다. 현재는 링크만 제공되어 있어 실제 기사 내용이 없으므로 번역을 진행할 수 없습니다. 텍스트를 복사해서 보내주시면 바로 도와드리겠습니다.

AI 코딩 어시스턴트 vs. 프로젝트 규칙

AI 코딩 어시스턴트는 코드를 빠르게 구현하지만, 교육된 기본값을 따를 뿐 프로젝트 규칙을 따르지 않습니다. 팀에서는 다음과 같은 사항을 알지 못합니다:

  • git reset --hard 명령을 금지합니다
  • GPG‑서명 커밋을 요구합니다
  • 사용자의 사용자명과 다른 GitHub 조직을 사용합니다

Claude Code의 PreToolUse 훅 시스템은 도구 호출을 실행 전에 가로채고, 위험한 작업을 차단하며, 상황에 맞는 가이드를 삽입합니다. 이 문서에서는 네 개의 특수화된 훅으로 구성된 실제 구현을 단계별로 살펴봅니다.

버전 노트

이 기사에서 사용된 additionalContext 기능은 Claude Code v2.1.9(2026년 1월 16일 출시) 이상이 필요합니다. 이전 버전은 차단 훅은 지원했지만 컨텍스트 주입은 지원하지 않았습니다. 이 기능은 feature request #15345에서 시작되었습니다.

Hook 계약

PreToolUse 훅은 stdin에서 JSON을 받아들이고 종료 코드와 stdout/stderr를 통해 통신하는 실행 파일입니다:

{
  "tool_name": "Bash",
  "tool_input": { "command": "git reset --hard HEAD~3" }
}
종료 코드의미출력
0허용stdout에서 additionalContext를 파싱합니다“allow with context” 형식을 아래에서 확인
2차단stderr가 사용자에게 표시됩니다

Allow‑with‑Context 출력 형식

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "additionalContext": "Your reminder or context here"
  }
}

Hook 1 – 안전 가드

첫 번째 훅은 파괴적인 작업을 실행되기 전에 차단합니다.

BLOCKING_RULES: list[SafetyRule] = [
    SafetyRule(
        pattern=r"git\s+reset\s+--hard",
        action="block",
        message=(
            "BLOCKED: Use `git stash` or `git rebase` instead. "
            "`git reset --hard` destroys uncommitted work."
        ),
    ),
    SafetyRule(
        pattern=r"--no-gpg-sign",
        action="block",
        message="BLOCKED: GPG signing is mandatory. Fix GPG issues, don't bypass.",
    ),
    SafetyRule(
        pattern=r"git\s+(?:add\s+)?(?:-A|--all|\.\s*$)",
        action="block",
        message=(
            "BLOCKED: Explicitly stage files to avoid committing unwanted files."
        ),
    ),
]

Why?
변경 사항을 커밋하도록 요청받을 때, Claude는 종종 git add -A를 실행해 모든 파일을 스테이징합니다. 빌드 아티팩트나 .env 파일이 있는 저장소에서는 절대 커밋해서는 안 되는 파일까지 스테이징될 수 있습니다. 명시적인 스테이징을 강제함으로써 의도된 커밋만 이루어지도록 합니다.

경고 규칙 (컨텍스트와 함께 허용)

WARNING_RULES: list[SafetyRule] = [
    SafetyRule(
        pattern=r"git\s+push\s+--force(?!\s*-with-lease)",
        action="warn",
        message="WARNING: Use `--force-with-lease` for safer force push.",
    ),
    SafetyRule(
        pattern=r"\.env",
        action="warn",
        message="WARNING: This file may contain secrets. Never commit .env files.",
    ),
]

결정 로직

def check_command(command: str) -> tuple[str, str]:
    # Check blocking rules first
    for rule in BLOCKING_RULES:
        if rule.matches(command):
            return ("block", rule.message)

    # Then warning rules
    for rule in WARNING_RULES:
        if rule.matches(command):
            return ("warn", rule.message)

    # Default: allow
    return ("allow", "")

Hook 2 – GitHub API 호출을 위한 컨텍스트 주입

리포지토리 경로가 항상 GitHub 조직 이름과 일치하는 것은 아닙니다.

  • Local checkout: ~/dev/AcmeCorp/webapp
  • GitHub owner: AcmeCorp (사용자 jdoe 가 아님).

특별한 조치를 취하지 않으면 Claude가 API 호출 시 잘못된 소유자를 기본값으로 사용합니다. 이 훅은 Claude가 GitHub MCP 도구를 사용할 때마다 리포지토리 메타데이터를 주입합니다.

GITHUB_BASE_CONTEXT = """CONTEXT: This repo's GitHub owner is 'AcmeCorp', NOT 'jdoe'.
Always use owner='AcmeCorp' in GitHub API calls.
Repository name: webapp
Default branch: main"""

PROJECT_BOARD_CONTEXT = """
Project board ID: PVT_xxxxxxxxxxxxxxxxxxxx
Epic Priority field ID: PVTF_xxxxxxxxxxxxxxxxxxxxxxx
Status field ID: PVTSSF_xxxxxxxxxxxxxxxxxxxxxxx

Note: gh project item-edit silently fails for Number fields. Use GraphQL mutations instead."""

Hook Matcher

{
  "matcher": "mcp__github__.*",
  "hooks": [
    { "type": "command", "command": "python3 /path/to/context-injection.py" }
  ]
}

프로젝트 관련 도구들도 추가적인 보드‑ID 컨텍스트를 받게 됩니다.

Hook 3 – 워크플로 규칙 적용

일부 워크플로 규칙은 린터로는 강제할 수 없습니다(예: TDD 규율, 워크트리 사용, 커밋 서명). 이 훅은 작업을 차단하지 않으면서 적절한 시점에 알림을 표시합니다.

GPG_REMINDER = WorkflowReminder(
    message="GPG REMINDER: Never use `--no-gpg-sign`. If GPG fails, check sandbox mode."
)

TDD_REMINDER = WorkflowReminder(
    message=(
        "TDD REMINDER: Is there a failing test? Write the RED test first, "
        "then write minimal code to make it GREEN."
    )
)

WORKTREE_REMINDER = WorkflowReminder(
    message=(
        "WORKTREE REMINDER: You appear to be working directly on main. "
        "Use `git worktree add .worktrees/issue-XX-description -b issue-XX-description`"
    )
)

알림 삽입 로직

def get_write_edit_reminders(file_path: str) -> list[str]:
    reminders = []

    # TDD reminder for Go production code
    if is_go_production_code(file_path):
        reminders.append(TDD_REMINDER.message)

    # Worktree reminder if editing production code outside a worktree
    if not is_in_worktree() and is_production_code_path(file_path):
        reminders.append(WORKTREE_REMINDER.message)

    return reminders

Hook 4 – (선택 사항) 사용자 정의 확장

You can add further hooks for:

  • Secret scanning – 명령이 알려진 비밀 패턴을 참조할 경우 차단하거나 경고합니다.
  • Dependency checksnpm install을 실행하기 전에 승인된 패키지 버전에 대한 컨텍스트를 주입합니다.
  • CI/CD gatinggit push를 허용하기 전에 로컬 테스트가 성공적으로 실행되었는지 요구합니다.

Each new hook follows the same contract: read JSON from stdin, decide via exit code, and optionally return additionalContext.

요약

이 네 가지 PreToolUse 훅을 계층화하면 다음을 얻을 수 있습니다:

주요 목표결과
안전 가드파괴적인 명령 차단, 위험한 패턴에 대한 경고데이터 손실 방지, 모범 사례 경고 표시
GitHub 컨텍스트 주입Claude의 API 호출을 실제 저장소 메타데이터와 일치시킴올바른 owner/board ID 확보, 잘못된 API 호출 방지
워크플로 규칙 적용팀별 프로세스를 개발자에게 상기시킴TDD, worktree 사용, GPG 서명을 차단 없이 강화
맞춤형 확장추가 프로젝트 제약에 맞춤유연하고 미래 지향적인 적용

이 훅 체인을 구현하면 Claude Code가 조직의 정책을 준수하면서도 AI 코딩 어시스턴트에게 기대하는 빠른 지원을 제공할 수 있습니다.

reminders.append(WORKTREE_REMINDER.message)

return reminders

워크트리 확인

워크트리 확인은 환경 변수를 사용하여 현재 디렉터리를 감지합니다:

def is_in_worktree() -> bool:
    cwd = os.environ.get("PWD", os.getcwd())
    return ".worktrees/" in cwd

Source:

Hook 4: 스킬 제안 엔진

Claude Code는 스킬(특정 도메인에 대한 재사용 가능한 명령 세트)을 지원합니다. 스킬‑제안 훅은 파일 패턴을 관련 스킬에 매핑하여 Claude가 진행하기 전에 도메인‑특화 가이드를 로드하도록 합니다.

SKILL_TRIGGERS: list[SkillTrigger] = [
    # Test files need test classification + TDD patterns
    SkillTrigger(
        skills=("classifying-test-sizes", "developing-with-tdd"),
        path_contains="_test.go",
    ),
    # Temporal workflows need TDD + Temporal + Go monorepo skills
    SkillTrigger(
        skills=("developing-with-tdd", "developing-with-temporal", "working-with-go-monorepo"),
        path_contains="apps/workers/",
        extension=".go",
    ),
    # Database migrations
    SkillTrigger(
        skills=("working-with-atlas-migrations",),
        path_contains="db/migrations/",
    ),
]

트리거 리스트는 첫 번째 매치가 우선되는 순서를 사용합니다. 구체적인 패턴(테스트 파일, 마이그레이션)은 일반적인 패턴(apps/ 내 모든 Go 파일)보다 먼저 배치됩니다. 이를 통해 _test.go 파일은 일반적인 Go 가이드가 아니라 테스트‑전용 스킬을 트리거하도록 보장합니다.

출력은 Claude에게 관련 스킬을 호출하도록 상기시킵니다:

if skills:
    skill_list = ", ".join(f"'{s}'" for s in skills)
    output = {
        "hookSpecificOutput": {
            "additionalContext": (
                f"SKILL REMINDER: Before proceeding, invoke these skills: {skill_list}. "
                f"They contain patterns, gotchas, and best practices for this file type."
            )
        }
    }

훅 구성

settings.json 파일은 어떤 툴에 어떤 훅이 실행될지를 조정합니다:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Write|Edit",
        "hooks": [{ "type": "command", "command": "python3 .../suggest-skills.py" }]
      },
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "python3 .../safety-guards.py" },
          { "type": "command", "command": "python3 .../workflow-rules.py" }
        ]
      },
      {
        "matcher": "Write|Edit",
        "hooks": [{ "type": "command", "command": "python3 .../workflow-rules.py" }]
      },
      {
        "matcher": "mcp__github__.*",
        "hooks": [{ "type": "command", "command": "python3 .../context-injection.py" }]
      }
    ]
  }
}
  • matcher 필드는 정규표현식 패턴을 허용합니다.
  • Bash 명령은 두 가지 안전 가드와 워크플로 규칙을 모두 받습니다.
  • Write/Edit 작업은 워크플로 규칙 스킬 제안을 트리거합니다.
  • GitHub MCP 도구는 컨텍스트 인젝션을 받습니다.

Observations

  • 내 워크플로우에서, 훅이 Claude가 저장소와 상호작용하는 방식을 바꾸었습니다:
  • Claude는 더 이상 git reset --hard를 시도하지 않습니다. 안전 훅이 이를 차단하고 대안을 제시합니다.
  • GitHub API 호출이 기본적으로 올바른 소유자를 사용합니다. 컨텍스트 훅을 적용하기 전에는 약 다섯 번 중 한 번 정도 소유자를 수동으로 수정했습니다.
  • TDD 알림이 모든 Go 파일 편집 시 나타납니다. 준수 여부를 정량화할 수는 없지만, 이 알림 덕분에 레드‑테스트 단계를 건너뛰는 것이 우연이 아니라 의도적인 것으로 느껴집니다.
  • 워크플로 파일을 편집하면 스킬 로딩이 자동으로 이루어집니다. 별도로 호출을 기억할 필요가 없습니다.

구현 고려사항

  • 훅은 도구 입력을 수정할 수 없으며, 허용, 차단, 또는 컨텍스트 주입만 할 수 있습니다.
  • 여러 파일을 읽어야 하는 복잡한 검증은 모든 도구 호출을 느리게 합니다.
  • 훅은 개별 도구 호출만을 보고, 더 넓은 대화 컨텍스트는 보지 못합니다.
  • 오류 처리 기본값은 관대합니다: JSON‑파싱 실패 시 종료 코드 0(허용)으로 처리되어 작업을 차단하지 않습니다. 이는 잘못된 형식의 훅이 전체 개발 워크플로를 중단하는 것을 방지합니다.

전체 구현은 400줄 이하인 네 개의 파이썬 파일로 구성됩니다. 각 훅은 단일 책임을 가지므로 테스트와 수정이 간단합니다. 새로운 관심사를 추가하려면 새로운 훅을 추가하면 되며, 각 훅은 집중된 역할을 유지합니다.

Back to Blog

관련 글

더 보기 »