PreToolUse/PostToolUse hooks silently disabled in subagents due to CLAUDE_CODE_SIMPLE check in _R()

Resolved 💬 4 comments Opened Apr 4, 2026 by barthazian Closed May 27, 2026

Bug Description

PreToolUse and PostToolUse hooks configured in ~/.claude/settings.json do not fire for tool calls made by subagents spawned via the Agent tool. The hooks work correctly for the main/parent agent session.

Root Cause (traced in cli.js v2.1.92)

The universal hook runner _R() has this guard at the top:

async function*_R({hookInput, toolUseID, matchQuery, signal, ...}) {
    if (S18()) return;                                // disableAllHooks policy
    if (U6(process.env.CLAUDE_CODE_SIMPLE)) return;   // <-- THIS LINE
    ...
}

CLAUDE_CODE_SIMPLE is set to "1" when --bare mode is active. Subagents appear to run in a context where this env var is truthy, causing _R() to return immediately without executing any hooks.

The bb6() function that checks hook existence does NOT filter by agent context — it correctly finds settings hooks for subagents. But execution never reaches bb6() because _R() bails out first.

Reproduction

  1. Configure a PreToolUse:Bash command hook in ~/.claude/settings.json:
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{"type": "command", "command": "/path/to/my-hook.sh"}]
    }]
  }
}
  1. In the main agent, run a Bash command → hook fires correctly
  2. Spawn a subagent via the Agent tool that runs Bash commands → hooks do NOT fire

Tested on v2.1.92, Windows 11, with both user-level and project-level settings.

Expected Behavior

PreToolUse and PostToolUse hooks should fire for tool calls made by subagents, same as for the main agent. The CLAUDE_CODE_SIMPLE guard in _R() should be scoped to only skip hooks when the current execution context is in simple/bare mode, not when any part of the process has set this env var.

Proposed Fix

Option A (minimal): In _R(), check a context-scoped flag instead of the global env var:

// Instead of:
if (U6(process.env.CLAUDE_CODE_SIMPLE)) return;

// Check the toolUseContext for simple mode:
if (O?.isSimpleMode || (!O && U6(process.env.CLAUDE_CODE_SIMPLE))) return;

Option B (broader): Don't use process.env for in-process state. Use a context object passed through the call chain.

Impact

  • Security: Restrictions enforced via PreToolUse hooks (e.g., blocking dangerous commands, validating file writes) can be completely bypassed by spawning a subagent
  • Functionality: Custom hook workflows (code quality checks, edit context injection, timeout guards) silently stop working in subagents with no error or warning
  • Debugging difficulty: No log message indicates hooks were skipped. The _R() function returns silently.

Related Issues

  • #34692 — PreToolUse/PostToolUse hooks do not fire for subagent tool calls
  • #21460 — [SECURITY] PreToolUse hooks not enforced on subagent tool calls
  • #18392 — Hooks in agent frontmatter not executed for subagents
  • #17688 — Skill-scoped hooks not triggered within plugins
  • #27755 — SubagentStart/SubagentStop unreliable

Environment

  • Claude Code v2.1.92
  • Windows 11
  • Hooks configured in ~/.claude/settings.json (user-level)
  • Verified by extracting and reading cli.js from npm package @anthropic-ai/claude-code@2.1.92

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗