When looking into Claude's issues...

122.56.***.***
14

Recently, I was trying to solve the issue of Claude modifying files without user consent. I created a hook for this purpose.
Then I thought: "What happens if the last task given to a sub-agent is a modification?"
When I checked, it turned out that if the sub-agent's work is blocked by the hook during its operation, the sub-agent's work history is blocked and reported as failed.
It's a waste of tokens.
Fortunately, I found a branch that can distinguish sub-agents, and by setting up a branch in the hook to trigger an "ask" before modification, I was able to solve the problem.
I was worried that the "ask" would be ignored in yolo mode, so I tested it, but fortunately, setting the "ask" in the hook overrides it, confirming normal operation.
Since I was already looking into Claude's code, I decided to also look into the previously installed read_once hook. The read_once hook is structured so that if a file is read twice, a warning is sent to the LLM. However, I had doubts about whether it actually worked.
When I tested it, no warning was actually sent to the LLM.
Upon further investigation, I discovered that the field of the hook was incorrect. When I asked Claude to test it, he suddenly started digging into the Claude binary.
Somehow, I managed to solve the problem, and then a sudden thought occurred: "Could we also do reverse engineering with the Claude Code source?"
I told Claude the location of the source code and asked him to check for anything applicable to our hook. He discovered many undocumented hook options.
I've tested some of them, so feel free to take a look if you're interested.

🔌 Claude Code Hook Output Field Complete Summary

Reference for hook output fields, verified through source code (src/types/hooks.ts v2.1.88) + binary analysis (v2.1.123) + live experiments.

Field Role Comparison

Field

Location

Target

Action

Verification

systemMessage

Top Level

Human (TUI Display)

Displayed in UI only, not injected into AI context

hookSpecificOutput.additionalContext

Inside hookSpecificOutput

AI Model

Injected into AI context with

hookSpecificOutput.permissionDecisionReason

Inside hookSpecificOutput

Logs/UI

Not injected into AI context on allow, injected on deny

hookSpecificOutput.updatedInput

Inside hookSpecificOutput

Tool Input

Replaces input value before tool execution

hookSpecificOutput.updatedMCPToolOutput

Inside hookSpecificOutput

MCP Tool Output

Replaces MCP tool result value

Unverified

Source Code Basis (src/types/hooks.ts)

// Top Level Field
systemMessage: z.string().describe('Warning message shown to the user').optional()

// Inside hookSpecificOutput — PreToolUse
z.object({
  hookEventName: z.literal('PreToolUse'),
  permissionDecision: permissionBehaviorSchema().optional(),
  permissionDecisionReason: z.string().optional(),
  updatedInput: z.record(z.string(), z.unknown()).optional(), // Tool input modification
  additionalContext: z.string().optional(),                    // AI context injection
})

// Inside hookSpecificOutput — PostToolUse
z.object({
  hookEventName: z.literal('PostToolUse'),
  additionalContext: z.string().optional(),
  updatedMCPToolOutput: z.unknown().optional(), // MCP output modification
})

Internal Processing Flow (src/utils/hooks.ts):

// systemMessage → hook_system_message (TUI display only)
if (result.systemMessage) {
  yield { message: createAttachmentMessage({ type: 'hook_system_message', ... }) }
}

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

Verified Output Format

PreToolUse warn mode (allow + AI context injection)

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

PreToolUse deny mode

{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Block reason"
  }
}

PreToolUse updatedInput — Tool input modification

# Intercept and modify Bash commands
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": "Content to inject into AI context after tool execution"
  }
}

Live Experiment Results (2026-05-01)

Experiment 1: additionalContext injection verification

additionalContext with instruction ACKNOWLEDGE: Write X on the first line of the response inserted → AI instruction execution confirmed.

Experiment 2: updatedInput — Bash command modification ✅

# Actual executed command
echo HOOK_TEST_ORIGINAL
# Output
HOOK_TEST_OVERRIDDEN   ← hook replaced

Experiment 3: updatedInput — Read file path redirection ✅

Read("/tmp/REDIRECT_SOURCE.txt") request
→ hook replaces file_path with "/tmp/REDIRECT_TARGET.txt"
→ TARGET file content returned

Experiment 4: PostToolUse additionalContext ✅

After Bash tool execution, hook's additionalContext injected as into AI context confirmed.

additionalContext supported hook events

Events with additionalContext field in source code:

Other Notable Fields

hookEventName

additionalContext

PreToolUse

PostToolUse

PostToolUseFailure

UserPromptSubmit

SessionStart

Setup

SubagentStart

Notification

Field

hook

Description

updatedInput

PreToolUse

Tool input manipulation (Bash command, Read file_path etc.)

updatedMCPToolOutput

PostToolUse

Replace MCP tool output value

initialUserMessage

SessionStart

Inject the first message at session start

watchPaths

SessionStart / CwdChanged

Register file watch paths

retry

PermissionDenied

Whether to retry after permission denial

updatedPermissions

PermissionRequest

Dynamically change permissions

read-once hook application details

~/.claude/read-once/hook.sh warn mode added additionalContext (2026-05-01):

  # Before change (AI context not injected)
jq -cn --arg r "$REASON" \
  '{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'

# After change (AI context injected)
jq -cn --arg r "$REASON" \
  '{"systemMessage":$r,"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow","additionalContext":$r}}'
  

diff path (warn mode) also modified + heredoc → jq replacement (special character safety).

🤖 Claude Code Sub-agent Hook Detection and Control Pattern

Pattern for detecting and controlling sub-agents in Claude Code when they attempt file modifications (Edit/Write/Bash writing) using the PreToolUse hook. Based on experimental observations and official documentation.


Key Findings

1. Session ID Sharing

Sub-agents share the same session_id with their parent. Since the UserPromptSubmit hook only executes when there is a user message, sub-agents do not create separate prompt files. Consequently, they read the parent's user message file as is.

2. Detecting Sub-Agents using the agent_id Field

The JSON received by the PreToolUse hook can be used to distinguish between a main agent and a sub-agent:

Field

Main Agent

Sub-agent

Logic Used

agent_id

None

Present (e.g., aaa6abfab...)

✅ Used

agent_type

None

Present (e.g., general-purpose)

❌ Not Used

Official documentation confirmation: "agent_id and agent_type are populated when the hook fires inside a subagent."

While agent_type was observed to be general-purpose in arbitrarily called sub-agents, its distinction from skill-based calls is unclear. Therefore, only the presence or absence of agent_id is used for detection, as it provides sufficient reliability.

3. Sub-Agents Do Not Automatically Inherit Parent Agent Permissions

Official documentation: "Subagents do not automatically inherit parent agent permissions."

When a skill is called, the user message includes the skill name and passes through the whitelist. However, sub-agents spawned autonomously by Claude based on the parent user message may accidentally pass through or be blocked.


Comparison of permissionDecision Actions

Value

Action

AI Context Injection

Just pass through

Completely block

Reason is passed to the sub-agent

User UI approval dialog

Not passed to AI

Defer for later (TypeScript only)

Priority: > > >

Note

goes directly to the user UI and is not injected into the AI context. passes a reason to the sub-agent context, allowing the sub-agent to understand and report the reason.


Application Patterns

Code that detects sub-agents and branches to (shell command hook):

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

    # Sub-agent detection → branch to 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]}) is requesting file modification: {file_path}"
        print(json.dumps({
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "ask",
                "permissionDecisionReason": reason
            }
        }))
        sys.exit(0)

    # Remaining logic (for main agent)
    ...

Warning

may be automatically approved when is . Check the environment's permission_mode if actual user confirmation is required.

Important

Even in yolo mode, hook is not bypassed. Yolo mode automatically approves Claude Code's general permission prompts, but hooks intervene at a higher layer. The hook's always displays a user dialog regardless of whether yolo mode is active or not. → A reliable safety mechanism for controlling sub-agent modifications.


Hooks Applied to This Bolt

File

Target

Applied Content

Edit / Write

sub-agent →

Bash write command

sub-agent →


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

개발한당

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

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

다가오는 한인 행사일정

  • 등록 된 일정이 없어요!