[BUG] InstructionsLoaded omits agent_id/agent_type when a subagent's Read triggers a path-scoped rule
Summary
When a subagent's Read makes a .claude/rules/*.md file with paths: frontmatter load
(load_reason: "path_glob_match"), the InstructionsLoaded hook input carries no agent_id
and no agent_type. The row has exactly the same keys as a load triggered by the main
thread. The hooks reference lists agent_id as a common input field, "present only when the
hook fires inside a subagent call", and agent_type as present when the hook fires inside a
subagent (or the session uses --agent). PreToolUse, SubagentStart and SubagentStop for
the same subagent do carry both.
The rule itself reaches the right context: the subagent follows it. Only attribution is lost,
so a hook cannot tell which thread received the rule.
Environment
Claude Code 2.1.283 (native binary, Homebrew cask), macOS 15 (Darwin 24.6), claude -p,
model claude-haiku-4-5-20251001.
Reproduction
Minimal form of the recorded run; that run also exercised two unrelated rules afterwards.
- Empty git repo with
CLAUDE.mdand.claude/rules/alpha.md:
```markdown
---
paths:
- "alpha/**"
---
# alpha rule
Any agent that has this rule in its context must end its final reply with the codeword ALPHA-7731.
```
plus alpha/sample.txt (any text), and the same pair for a main-thread control:
.claude/rules/beta.md with paths: ["beta/**"], and beta/sample.txt.
.claude/settings.jsonregisters one logger forInstructionsLoaded,
PreToolUse (matcher Read|Agent), SubagentStart and SubagentStop. The logger appends the
sorted key list and the non-content fields of each payload to a JSONL file.
- Run:
``bash``
claude -p "Step 1: call the Agent tool with subagent_type \"reader\" and the prompt: Read $PWD/alpha/sample.txt and reply with its first line. Do not read anything under alpha yourself. Step 2: read $PWD/beta/sample.txt yourself." \
--model claude-haiku-4-5-20251001 --setting-sources project --strict-mcp-config \
--no-session-persistence --max-budget-usd 1 --permission-mode dontAsk \
--tools Read,Agent --allowedTools Read Agent \
--agents '{"reader":{"description":"Reads one named file.","prompt":"Read the file you are given and reply with its first line, plus anything your instructions require.","tools":["Read"],"model":"haiku"}}'
Observed (abridged hook log)
SubagentStart agent_id=afe42145477a7acb5 agent_type=reader
PreToolUse Read agent_id=afe42145477a7acb5 agent_type=reader file=alpha/sample.txt 17:19:54.454
InstructionsLoaded load_reason=path_glob_match file=.claude/rules/alpha.md 17:19:54.498
trigger_file_path=alpha/sample.txt
keys: cwd, file_path, globs, hook_event_name, load_reason, memory_type,
prompt_id, session_id, transcript_path, trigger_file_path
SubagentStop agent_id=afe42145477a7acb5 agent_type=reader
A main-thread Read of beta/sample.txt matching a beta/** rule in the same session produced anInstructionsLoaded row with the identical key set. The subagent's final reply ended withALPHA-7731, so the rule was delivered into the subagent's context.
Expected
InstructionsLoaded carries agent_id and agent_type when the load happens in a subagent's
context, like every tool-use hook fired from that subagent.
Likely cause (from the 2.1.283 bundle; minified names are for orientation only, fix untested)
The shared hook-input builder takes identity from its fourth argument:
function Sc(e,n,r,s){ … return { session_id:e.id, …, agent_id:s?.agentId, agent_type:g, … } }
PreToolUse passes the tool-use context: {...Sc(s.session, oe(), g, s), hook_event_name:"PreToolUse", …}.
The InstructionsLoaded emitter does not:
async function dZt(e,n,r,s,g){ … q={...Sc(e,oe()), hook_event_name:"InstructionsLoaded", file_path:n, … } … }
Its lazy-load caller holds that context but passes only its session:dZt(n.session, b.path, b.type, F, {globs:b.globs, triggerFilePath:r, …}). Passing that context
through to Sc, as #91910's comments propose for PreCompact, should populate both fields; this
proposed fix has not been tested.
Impact
A hook that enforces "read the rule before writing matching files" per thread (keyed onsession_id + agent_id) is released for the main thread by a subagent's read, even
though the rule went into the subagent. It never records the load for the subagent itself,
so the subagent is blocked even though it has the rule. Load-reliability telemetry cannot
report subagent delivery at all.
Related
- #91910 — the same missing identity on
PreCompact/PostCompact/SessionStart(compact).
One comment there notes InstructionsLoaded and Notification lack it too, without a repro.
- #16424 — the original request to expose agent context in hook payloads (closed as completed).