claude 이슈를 파보면서...

122.56.***.***
6

최근 claude의 이슈들중 사용자 승인 없이 파일을 맘대로 수정하는 걸 해결하기 위해 hook을 만들어서 사용하고 있습니다
그런데 이런 생각이들더군요 "서브에이전트에게 지시한 작업의 마지막이 수정이면 어떻게 되는거지?"
확인해보니 서브에이전트의 작업중 hook으로 블락을 당하면 서브에이전트의 작업 내역은 블락되어 실패 되었다는 보고와 함께 종료가되더군요
토큰의 낭비가 되는 샘이죠
다행이 서브에이전트를 구분할 수 있는 분기를 발견하고, 서브에이전트의 경우 hook에서 분기를 태워 수정 전 ask를 발동하게 해서 해결 했습니다
yolo 모드에서 ask가 무시될걸 우려해 테스트해봤으나 다행이 hook에서 ask를 설정하면 오버라이드되서 정상작동을 확인했죠
기왕 파보는 김에 과거에 설치한 read_once훅도 파보자고 생각해서 read_once훅의 경우 파일을 두번째 읽으면 llm에게 경고가 가는 구조인데 이게 실제로 동작을 하는지가 의문이었어요
테스트 해보니 실제로 llm에게 경고가 가지 않더군요
이것도 까보다 보니 해당 훅의 필드가 잘못되어있었고, claude한테 이거 테스트 해보자 하니깐 갑자기 이녀석이 claude 바이너리를 까기 시작하더라구요
그래서 어찌어찌 해결하고, 갑자기 드는 생각 "Claude Code 유출 소스도 있으니 이걸로 역공학도 가능하겠는데?"
claude한테 소스코드 위치 알려주고 우리 훅에 적용 할만한것 확인하라고 했더니 매뉴얼에 없는 훅의 옵션들을 다량 발견해오더군요. 일부는 테스트도 해봤구
테스트한 내용 공유 드리니 심심하면 한번 봐주세요

🔌 Claude Code Hook 출력 필드 완전 정리

소스 코드(src/types/hooks.ts v2.1.88) + 바이너리 분석(v2.1.123) + 라이브 실험으로 검증한 hook 출력 필드 레퍼런스.

필드 역할 비교

필드

위치

대상

동작

검증

systemMessage

최상위

사람 (TUI 표시)

UI에만 표시, AI context 미주입

hookSpecificOutput.additionalContext

hookSpecificOutput 내부

AI 모델

로 AI context 주입

hookSpecificOutput.permissionDecisionReason

hookSpecificOutput 내부

로그/UI

allow 시 AI context 미주입, deny 시 AI에 전달

hookSpecificOutput.updatedInput

hookSpecificOutput 내부

도구 입력

도구 실행 전 입력값 교체

hookSpecificOutput.updatedMCPToolOutput

hookSpecificOutput 내부

MCP 도구 출력

MCP 도구 결과값 교체

미검증

소스 코드 근거 (src/types/hooks.ts)

// 최상위 필드
systemMessage: z.string().describe('Warning message shown to the user').optional()

// hookSpecificOutput 내부 — PreToolUse
z.object({
  hookEventName: z.literal('PreToolUse'),
  permissionDecision: permissionBehaviorSchema().optional(),
  permissionDecisionReason: z.string().optional(),
  updatedInput: z.record(z.string(), z.unknown()).optional(),  // 도구 입력 변조
  additionalContext: z.string().optional(),                    // AI context 주입
})

// hookSpecificOutput 내부 — PostToolUse
z.object({
  hookEventName: z.literal('PostToolUse'),
  additionalContext: z.string().optional(),
  updatedMCPToolOutput: z.unknown().optional(),  // MCP 출력 변조
})

내부 처리 흐름 (src/utils/hooks.ts):

// systemMessage → hook_system_message (TUI 표시 전용)
if (result.systemMessage) {
  yield { message: createAttachmentMessage({ type: 'hook_system_message', ... }) }
}

// additionalContext → hook_additional_context (AI context 주입)
if (result.additionalContext) {
  yield { additionalContexts: [result.additionalContext] }
}
// → createAttachmentMessage({ type: 'hook_additional_context', content: [...] })

검증된 출력 형식

PreToolUse warn 모드 (allow + AI context 주입)

jq -cn --arg r "$REASON" \
  '{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":$r}}'

PreToolUse deny 모드

{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "차단 이유"
  }
}

PreToolUse updatedInput — 도구 입력 변조

# Bash 명령어 가로채서 변조
print(json.dumps({
    "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "allow",
        "updatedInput": {
            "command": "echo OVERRIDDEN"   # Bash tool
            # "file_path": "/new/path"    # Read tool
        }
    }
}))

PostToolUse additionalContext

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "도구 실행 후 AI context에 주입할 내용"
  }
}

라이브 실험 결과 (2026-05-01)

실험 1: additionalContext 주입 검증

additionalContextACKNOWLEDGE: 응답 첫 줄에 X를 적어라 지시 삽입 → AI가 지시 수행 확인.

실험 2: updatedInput — Bash 명령어 변조 ✅

# 실제 실행한 명령어
echo HOOK_TEST_ORIGINAL
# 출력
HOOK_TEST_OVERRIDDEN   ← hook이 교체

실험 3: updatedInput — Read 파일 경로 리다이렉트 ✅

Read("/tmp/REDIRECT_SOURCE.txt") 요청
→ hook이 file_path를 "/tmp/REDIRECT_TARGET.txt"로 교체
→ TARGET 파일 내용 반환

실험 4: PostToolUse additionalContext ✅

Bash 도구 실행 후 hook의 additionalContext로 AI context 주입 확인.

additionalContext가 지원되는 hook 이벤트

소스 코드 기준 additionalContext 필드가 있는 이벤트:

hookEventName

additionalContext

PreToolUse

PostToolUse

PostToolUseFailure

UserPromptSubmit

SessionStart

Setup

SubagentStart

Notification

그 외 주목할 필드

필드

hook

설명

updatedInput

PreToolUse

도구 입력 변조 (Bash command, Read file_path 등)

updatedMCPToolOutput

PostToolUse

MCP 도구 결과값 교체

initialUserMessage

SessionStart

세션 시작 시 첫 메시지 주입

watchPaths

SessionStart / CwdChanged

파일 감시 경로 등록

retry

PermissionDenied

권한 거부 후 재시도 여부

updatedPermissions

PermissionRequest

권한 동적 변경

read-once hook 적용 내역

~/.claude/read-once/hook.sh warn 모드에 additionalContext 추가 (2026-05-01):

# 변경 전 (AI context 미주입)
jq -cn --arg r "$REASON" \
  '{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'

# 변경 후 (AI context 주입)
jq -cn --arg r "$REASON" \
  '{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":$r}}'

diff 경로(warn 모드)도 동일하게 수정 + heredoc → jq 교체(특수문자 안전).

🤖 Claude Code Sub-agent Hook 감지 및 제어 패턴

Claude Code에서 sub-agent가 파일 수정(Edit/Write/Bash 쓰기)을 시도할 때 PreToolUse 훅으로 감지하고 제어하는 패턴. 실험으로 확인한 동작과 공식 문서 기반.


핵심 발견

1. Session ID 공유

Sub-agent는 parent와 동일한 session_id를 공유한다. UserPromptSubmit 훅은 유저 메시지가 있을 때만 실행되므로, sub-agent는 별도의 프롬프트 파일을 생성하지 않는다. 결과적으로 parent의 유저 메시지 파일을 그대로 읽게 된다.

2. agent_id 필드로 감지

PreToolUse hook이 받는 JSON에서 sub-agent 여부를 구분할 수 있다:

필드

메인 에이전트

Sub-agent

로직 사용

agent_id

없음

있음 (예: aaa6abfab...)

✅ 사용

agent_type

없음

있음 (예: general-purpose)

❌ 미사용

공식 문서 확인: "agent_id and agent_type are populated when the hook fires inside a subagent."

agent_type은 임의로 호출된 sub-agent에서 general-purpose로 관찰됐으나, 스킬 기반 호출과의 구분이 불확실하여 로직에는 사용하지 않는다. agent_id 유무만으로 감지하는 것이 충분하고 신뢰성이 높다.

3. Sub-agent는 parent 권한을 자동 상속하지 않음

공식 문서: "Subagents do not automatically inherit parent agent permissions."

스킬 호출의 경우 유저 메시지에 스킬명이 포함되어 화이트리스트를 통과하지만, Claude가 자율 판단으로 띄운 sub-agent는 parent 유저 메시지에 따라 우연히 통과되거나 블락된다.


permissionDecision 동작 비교

동작

AI 컨텍스트 주입

"allow"

그냥 통과

"deny"

완전 차단

Sub-agent에게 reason 전달됨

"ask"

사용자 UI 승인 다이얼로그

AI에게는 전달 안 됨

"defer"

나중으로 미룸 (TypeScript 전용)

우선순위: deny > defer > ask > allow

Note

ask사용자 UI로 직접 올라가며 AI 컨텍스트에 주입되지 않는다. deny는 sub-agent 컨텍스트에 reason이 전달되어 sub-agent가 이유를 파악하고 보고할 수 있다.


적용 패턴

Sub-agent를 감지해 ask로 분기하는 코드 (shell command hook):

raw = sys.argv[1]
try:
    d = json.loads(raw)

    # sub-agent 감지 → ask로 분기
    agent_id = d.get("agent_id", "")
    if agent_id:
        file_path = d.get("tool_input", {}).get("file_path", "?")
        reason = f"Sub-agent({agent_id[:8]})가 파일 수정을 요청합니다: {file_path}"
        print(json.dumps({
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "ask",
                "permissionDecisionReason": reason
            }
        }))
        sys.exit(0)

    # 이하 기존 로직 (메인 에이전트용)
    ...

Warning

askpermission_modeacceptEdits일 때 자동 승인될 수 있다. 실제 사용자 확인이 필요하면 환경의 permission_mode를 확인할 것.

Important

yolo 모드에서도 hook ask는 우회되지 않는다. yolo 모드는 Claude Code의 일반 permission prompt를 자동 승인하지만, 훅은 그보다 상위 레이어에서 개입한다. 훅의 ask는 yolo 모드 활성 여부와 무관하게 항상 사용자 다이얼로그를 표시한다. → sub-agent 수정 제어용으로 신뢰할 수 있는 안전장치.


이 볼트에 적용된 훅

파일

대상

적용 내용

~/.claude/scripts/hooks/pre-edit-question-block.sh

Edit / Write

sub-agent → ask

~/.claude/scripts/hooks/pre-bash-write-block.sh

Bash 쓰기 명령

sub-agent → ask


로그인한 회원만 댓글 등록이 가능합니다.

개발한당

KR | ID | EN
  • IDR
  • KOR
8.35 -0.01

2026.07.10 KEB 하나은행 고시회차 955회

다가오는 한인 행사일정

  • 등록 된 일정이 없어요!