[BUG] PreToolUse/PostToolUse inside a running subagent are sometimes reported under a different agent_id that has no SubagentStart, no agent_type, and never gets a SubagentStop

Status Open
Reported on v2.1.251
Maintainer reply None cached
Activity 0 comments · opened Aug 29, 2026

Preflight Checklist

  • [x] I have searched existing issues. Closest: #89555, #87065, #59719, #88995 (all about SubagentStop arriving with a fresh agent_id / empty agent_type). This report is about a different event class: PreToolUse/PostToolUse for tool calls of a real, running subagent arriving under an agent_id that never appears in any SubagentStart or SubagentStop at all.
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (2.1.251)

What's Wrong?

I run command hooks for SubagentStart, SubagentStop, PreToolUse, PostToolUse (matcher *), Stop, SessionStart, SessionEnd, UserPromptSubmit, Notification, PermissionRequest. Each hook forwards its stdin JSON to a local consumer that tracks subagents by agent_id: an entry is created on the first payload carrying an agent_id, agent_type is recorded whenever a payload carries it, tool_name/tool_input are recorded from PreToolUse/PostToolUse, and the entry is removed on SubagentStop with the same agent_id.

In one long-lived session (same session_id since 2026-08-06; on 2026-08-29 alone it spawned over 50 subagents with the Agent tool as asynchronous background subagents, and resumed several completed ones with SendMessage), five entries were created on 2026-08-29 between 07:20 and 16:09 UTC that:

  • were created by a PreToolUse/PostToolUse payload (they carry a tool_name),
  • never received any payload carrying agent_type (so no SubagentStart ever fired for that agent_id),
  • never received a SubagentStop, and are still there hours later,
  • three of them received payloads only for a short burst (about 1, 5 and 6 minutes) and then nothing; the other two kept receiving occasional payloads for an hour or more.

Every subagent the session really spawned (per ~/.claude/projects/<project>/<session>/subagents/agent-*.jsonl and .meta.json) got its own SubagentStart and SubagentStop under its own agent_id; all of those were matched and cleaned up normally.

Cross-referencing the stray entries' creation times and recorded tool_input against the subagent transcripts shows that the tool calls reported under the stray ids are tool calls of real, running subagents, whose other tool calls were reported under their real agent_id (with agent_type) as usual. In other words, a subagent's consecutive tool calls can be attributed to two different agent_ids, and the second id has no lifecycle events.

Cleanest case (all times UTC, 2026-08-29, ids truncated):

  • Subagent a37ed07… (agent_type: claude, spawnDepth: 1, background) runs continuously from 15:58:30. It has its own entry (created by its SubagentStart).
  • 16:09:19 it spawns a child with the Agent tool (child ad36795…, ran 16:09:21 to 16:14:32); 16:12:45 another child (afad1ed…, ran 16:12:47 to 16:14:59).
  • A new entry with an unknown agent_id and no agent_type appears at 16:09:43 (+/- 20 s).
  • 16:14:51 a37ed07… runs Bash: lsof -nP -iTCP -sTCP:LISTEN … (present in agent-a37ed07….jsonl as its own tool_use). The hook payload for that call is recorded on the stray entry, not on a37ed07…'s entry.
  • 16:15:06 a37ed07… runs Bash: cd ~/projects/<repo> && …. That payload is recorded on a37ed07…'s own entry again (agent_type: claude present).
  • The stray entry never receives another payload and never receives SubagentStop.

The other four cases (every subagent named below has its own entry, created by its own SubagentStart, and its own SubagentStop was later matched):

| stray entry created | subagent active at that moment | last payload recorded on the stray entry | which real call that matches |
|---|---|---|---|
| 07:20:45 +/- 1 min | aecdca5… (claude, ran 07:07:34 to 07:48:55, never resumed; its Bash: cd … && sed -n … is at 07:20:44) | Bash: cd ~/projects/<repo> && … | one of aecdca5…'s cd … && … calls between 07:20:44 and 07:21:49; nothing after 07:21:53 (its Read/Edit calls from then on were not recorded on the stray entry) |
| 08:35:45 +/- 1 min | a74c422… (claude, ran 08:31:12 to 09:04:20, never resumed) | Bash: cat > ~/… | no cat > command exists in a74c422…'s transcript at all. Calls of that shape exist later, by other subagents: a799181… (09:36 to 09:37), ab28539… (09:53:05), a37ed07… (16:14:04). So this stray id apparently kept receiving payloads for an hour or more, from a different subagent than the one active when it was created |
| 09:20:45 +/- 1 min | a355335… (claude, ran 09:09:48 to 11:40:45; its Bash: cat > /private/tmp/… is at 09:20:54) | Bash: npx tsc --noEmit … | not uniquely determined: a355335… ran it at 10:06:40, 11:27:45 and 11:39:49, and its children afb3c2d… (09:44:48), ab28539… (09:56 to 10:05) and af5b526… (10:08:45) ran it too. Either way the stray entry kept receiving payloads for at least 45 minutes after creation |
| 13:32:45 +/- 1 min | af2b998… (claude, ran 11:55:44 to 14:44:06; it resumed a completed child with SendMessage at 13:31:52) | SendMessage | af2b998…'s SendMessage at 13:31:52 or 13:38:12 (nobody else called SendMessage in that window); its Bash at 13:38:23 and everything after were not recorded on the stray entry |
| 16:09:43 +/- 20 s | a37ed07… (see above) | Bash: lsof -nP -iTCP … | a37ed07…'s lsof at 16:14:51 (the only lsof call by anyone in that window); its next Bash at 16:15:06 was recorded on its own entry |

I could not find a condition that predicts when the stray id is used. Two of the five began right after the subagent spawned or resumed a child (16:09:19 Agent tool; 13:31:52 SendMessage to a completed child) and stopped receiving payloads around the time that child finished, but another subagent (aadb709…) that spawned children in the same minutes was reported under its own id throughout, and two of the five subagents (aecdca5…, a74c422…) had no child running and were never resumed.

None of the subagent transcripts contain a compaction boundary in those windows, all Agent tool calls involved were single tool calls per assistant message (no parallel batches), and no type: prompt / type: agent hooks are configured (only command hooks).

What Should Happen?

Per the hook input schema, agent_type is "present when the hook fires from within a subagent (alongside agent_id)". Every PreToolUse/PostToolUse fired inside a subagent should carry that subagent's agent_id (the one its SubagentStart carried) and its agent_type. Any agent_id that appears in hook payloads should be bracketed by a SubagentStart before and a SubagentStop after; an id that gets neither leaves every agent_id-keyed consumer with an entry it can never close.

Error Messages/Logs

No error output. The consumer keeps state rather than a raw payload log, so I do not have the raw stray-id payloads; I can add a raw stdin-logging hook and attach them if that helps. The reconciliation above comes from the consumer's entries (creation time, agent_type never set, last tool_name/tool_input) cross-referenced with subagents/agent-*.jsonl, agent-*.meta.json, and the parent session transcript.

Steps to Reproduce

Not reproducible on demand; observed 5 times in one session.

  1. Register a PreToolUse/PostToolUse (matcher *), SubagentStart, SubagentStop hook that appends its stdin JSON to a file.
  2. Run a long session that spawns background subagents with the Agent tool, lets those subagents spawn their own children, and resumes completed subagents with SendMessage.
  3. Reconcile: collect every agent_id seen in PreToolUse/PostToolUse; for each, check whether a SubagentStart (with agent_type) precedes it and a SubagentStop follows it.
  4. Some ids will have neither. Match their tool_input and timestamps against ~/.claude/projects/<project>/<session>/subagents/agent-*.jsonl to see which real subagent made those calls.

Claude Model

Main session: claude-fable-5. Subagents: opus (per .meta.json).

Is this a regression?

Unknown. The same hooks have been in place for months and these entries had not accumulated before; I do not have a last known good version.

Last Working Version

Unknown

Claude Code Version

2.1.251

Platform

macOS

Operating System

macOS 26.5.1 (Darwin 25.5.0), Apple Silicon

Terminal/Shell

zsh, third-party terminal emulator (not the desktop app)

Additional Information

  • Related: #89555 (SubagentStop with fresh agent_id, empty agent_type, after parent Stop), #87065 (internal forks fire SubagentStop with empty agent_type), #59719, #88995. Those are about stop events with a stray id. Here the stray id shows up on PreToolUse/PostToolUse of a real subagent's own tool calls, and gets no start or stop at all, so it looks like a different producer (or the same producer also being used to execute the parent's tools).
  • The affected subagents were all agent_type: claude, spawnDepth: 1, asynchronous background subagents (the parent kept working while they ran), in a session that also uses SendMessage to resume completed subagents and the TaskStop/ListAgents tools.

View original on GitHub ↗