[BUG] InstructionsLoaded omits agent_id/agent_type when a subagent's Read triggers a path-scoped rule

Status Fixed / completed
Reported on v2.1.283
Maintainer reply None cached
Activity 0 comments · opened Sep 27, 2026 · closed Oct 1, 2026

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.

  1. Empty git repo with CLAUDE.md and .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.

  1. .claude/settings.json registers one logger for InstructionsLoaded,

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.

  1. 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 an
InstructionsLoaded row with the identical key set. The subagent's final reply ended with
ALPHA-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 on
session_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).

View original on GitHub ↗