You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(hooks): AgentInvocation logs every background Agent spawn as subagent_stop (duration 0) — async-spawn detection misses the current tool_response, and start records are gone before AgentWatchdog can count them #2387
LifeOS 7.40.4 (main @ 5e2f2e8), Claude Code 2.1.30x (observed 2026-10-23; Agent tool schema unchanged in 2.1.310).
What is broken
AgentInvocation.hook.ts runs on PreToolUse and PostToolUse for Agent. Since v1.4.0 it knows PostToolUse fires at spawn time for a background agent and detects that case by matching tool_response against /Spawned successfully|running and will receive instructions via mailbox/i: on a match it logs subagent_spawned_async, otherwise subagent_stop with a computed duration.
In the current harness the match never succeeds for a background spawn. Every dispatch produces a subagent_start and a subagent_stop with duration_seconds: 0 in the same second — 7 of 7 dispatches across two sessions on 2026-10-23 — so subagent-events.jsonl records every background agent as a zero-second run, and anything built on those events (durations, the Pulse agents view) sees agents that start and stop at once.
The transcript's tool_result for the same spawn does contain both phrases ("Spawned successfully. (This tool result is internal metadata …)\nagent_id: …\nname: …\nThe agent is now running and will receive instructions via mailbox."), so the PostToolUse tool_response payload the hook receives must differ from the transcript text. I have not captured the raw payload — the hook does not log it.
A second thing I noticed while tracing it: the start record is deleted at PostToolUse in both branches (:204-211 runs before the async check, and the v1.4.0 header says the spawn-ack removes it by design, liveness coming from subagents/agent-*.jsonl mtimes). That means AgentWatchdog.ts, which counts active agents from the starts file, sees zero during a background run even after #2362 fixes the file name — the record is gone within the spawn second. Confirmed: agent-starts.json was {} while a 13-minute agent was working.
Dispatch any background Agent (subagent_type: general-purpose, a brief that runs a few minutes).
tail -2 MEMORY/OBSERVABILITY/subagent-events.jsonl → subagent_start and subagent_stop with "duration_seconds":0, same timestamp.
cat MEMORY/OBSERVABILITY/agent-starts.json while the agent is still working → {}.
Suggested fix
Stop inferring the spawn from response text: with the current Agent tool every main-conversation dispatch is a background spawn, so PostToolUse:Agent is always the spawn-ack — log subagent_spawned_async there and take the real stop from the SubagentStop hook event (correlated by agent_id/name, since that payload lacks subagent_type/description), or from the transcript going quiet. If the response text stays a signal, log the raw tool_response shape once under a debug flag so the regex can be fixed against what the harness actually sends. For the watchdog, read liveness from the same subagents/agent-*.jsonl mtimes the header names instead of the starts file, so #2362 and this fix together give it something to watch.
Stop regex-matching tool_response. With the current Agent tool, every main-conversation dispatch is a background spawn, so PostToolUse:Agent is always the spawn-ack. Always log subagent_spawned_async there. Don't compute a duration and don't delete starts[key] (:204-211).
Take the real stop from the SubagentStop event. Log subagent_stop and compute the duration there, correlating by agent_id/name (that payload lacks subagent_type/description). The transcript going quiet also works as a fallback signal.
Fix the watchdog's liveness source. Make AgentWatchdog.ts:41 read subagents/agent-*.jsonl mtimes instead of agent-starts.json, which is {} within the spawn second. Combined with fix(watchdog): read agent-starts.json #2362, the watchdog then has something real to watch.
If response text stays a signal, log the raw tool_response shape once behind a debug flag, so the regex can be fixed against what the harness actually sends.
🤖 Kai (AI assistant) responding on behalf of Daniel Miessler
Thanks @IvanLeontev-stack, and @Shiv-61 for the proposal. Both halves have changed since 7.40.4. First, the spawn regex now matches the harness's current Async agent launched wording; that fix arrived in v7.51.0. Second, when the LifeOSHooks plugin is active, this command hook stands down. The plugin reads start and stop from Claude Code's own agent list: it logs subagent_start when an agent spawns, checks the list every two seconds, and logs one subagent_stop per run with the real duration and status. That's the approach @Shiv-61 suggested.
The watchdog reading the wrong file name is PR #2362, where we chose to retire the watchdog instead of fixing it. Deleting a background agent's start record when the spawn is acknowledged is by design, as the hook header explains. Closing.
Activity
Shiv-61 commentedon Oct 24, 2026
Hey @IvanLeontev-stack , Will this work
Stop regex-matching
tool_response. With the currentAgenttool, every main-conversation dispatch is a background spawn, so PostToolUse:Agent is always the spawn-ack. Always logsubagent_spawned_asyncthere. Don't compute a duration and don'tdelete starts[key](:204-211).Take the real stop from the
SubagentStopevent. Logsubagent_stopand compute the duration there, correlating byagent_id/name(that payload lackssubagent_type/description). The transcript going quiet also works as a fallback signal.Fix the watchdog's liveness source. Make
AgentWatchdog.ts:41readsubagents/agent-*.jsonlmtimes instead ofagent-starts.json, which is{}within the spawn second. Combined with fix(watchdog): read agent-starts.json #2362, the watchdog then has something real to watch.If response text stays a signal, log the raw
tool_responseshape once behind a debug flag, so the regex can be fixed against what the harness actually sends.danielmiessler commentedon Oct 28, 2026
Note
🤖 Kai (AI assistant) responding on behalf of Daniel Miessler
Thanks @IvanLeontev-stack, and @Shiv-61 for the proposal. Both halves have changed since 7.40.4. First, the spawn regex now matches the harness's current Async agent launched wording; that fix arrived in v7.51.0. Second, when the LifeOSHooks plugin is active, this command hook stands down. The plugin reads start and stop from Claude Code's own agent list: it logs subagent_start when an agent spawns, checks the list every two seconds, and logs one subagent_stop per run with the real duration and status. That's the approach @Shiv-61 suggested.
The watchdog reading the wrong file name is PR #2362, where we chose to retire the watchdog instead of fixing it. Deleting a background agent's start record when the spawn is acknowledged is by design, as the hook header explains. Closing.