fix(hooks): agent-tracker never ran — reads a non-existent env var - #130
Open
mt-alarcon wants to merge 1 commit into
Open
fix(hooks): agent-tracker never ran — reads a non-existent env var#130mt-alarcon wants to merge 1 commit into
mt-alarcon wants to merge 1 commit into
Conversation
`agent-tracker.sh` reads the hook event from `$CLAUDE_HOOK_EVENT`, which Claude
Code does not export. With EVENT empty no branch ever executes, so the hook has
never written a single entry — while being registered in settings.json and
exiting 0 on every invocation.
How to confirm in one command:
rg CLAUDE_HOOK_EVENT
It only appears in the hook that reads it, never in anything that sets it.
Proof by state: `.claude/agent-status.json` stays at its initialization value,
`{"active_agents":[],"last_updated":""}`. An empty `last_updated` means even the
`Stop` branch (which only writes a timestamp) never ran.
Three defects, each silent on its own:
1. The event source does not exist. Fixed by reading argv[1] — the same pattern
the plugin dispatcher already uses in settings.json — with a fallback to the
`hook_event_name` field of the stdin payload.
2. The payload parser could not match the real payload. It used
`grep -o '"tool_name":"[^"]*"'`, with no space after the colon, while the
actual payload is pretty-printed (`"tool_name": "Agent"`). Replaced by a json
parse. This defect was hidden behind the first one.
3. `$DESCRIPTION` was interpolated into `python3 -c '...'`, so a quote or
apostrophe in the description broke the script — and `2>/dev/null` swallowed
it. Values are now passed via argv.
Also adds a `PostToolUse` branch: the hook previously only appended on start and
cleared everything on `Stop`, so `active_agents` could never reflect what is
actually running — only what had ever started. Consumers reading "which agents
are running in real-time" got a list that only grew.
Verified with the real pretty-printed payload, with the payload-only fallback,
with a non-Agent tool, with malformed input (still exits 0, per the hook
contract), and against the previous version as a control — which writes nothing.
Reviewer's GuideFixes the agent activity tracker hook so it correctly receives hook events, robustly parses the JSON payload, safely handles descriptions, and adds support for PostToolUse to keep the active_agents list in sync with currently running agents. Sequence diagram for agent tracker hook event handlingsequenceDiagram
actor ClaudeCode
participant Hook as agent-tracker.sh
participant Python as python3_block
participant Status as agent-status.json
ClaudeCode->>Hook: bash agent-tracker.sh PreToolUse
ClaudeCode->>Hook: stdin JSON payload
alt EVENT passed as argv[1]
Hook->>Hook: EVENT=${1}
else EVENT missing
Hook->>Hook: EVENT from hook_event_name in payload
end
alt EVENT is PreToolUse or PostToolUse
Hook->>Python: python3 -c (parse payload)
Python->>Python: json.load
Python->>Python: check tool_name == Agent
alt EVENT is PreToolUse
Python->>Python: append active_agents entry
else EVENT is PostToolUse
Python->>Python: remove matching active_agents entry
end
Python->>Status: json.dump
else EVENT is Stop
Hook->>Status: echo cleared active_agents
end
Hook->>ClaudeCode: exit 0
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The PostToolUse removal logic relies on matching both
agentanddescription, which may be brittle if descriptions are not stable or can collide; consider adding a stable identifier (e.g., an ID from the payload) to reliably pair start/stop events. - The script now unconditionally reads all of stdin into
INPUT, even for events that do not require the payload (e.g.,Stop); if hooks may receive large payloads or be chained, consider guarding the read so it only occurs for events that actually need it.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The PostToolUse removal logic relies on matching both `agent` and `description`, which may be brittle if descriptions are not stable or can collide; consider adding a stable identifier (e.g., an ID from the payload) to reliably pair start/stop events.
- The script now unconditionally reads all of stdin into `INPUT`, even for events that do not require the payload (e.g., `Stop`); if hooks may receive large payloads or be chained, consider guarding the read so it only occurs for events that actually need it.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The
agent-tracker.shhook reads the event from$CLAUDE_HOOK_EVENT, a variable Claude Code does not export. WithEVENTempty, no branch ever executes — so the hook has never written a single entry, while being registered insettings.jsonand exiting 0 every time.Confirm in one command:
It appears only in the hook that reads it, never in anything that sets it.
Proof by state:
.claude/agent-status.jsonstays at its initialization value,{"active_agents":[],"last_updated":""}. An emptylast_updatedmeans even theStopbranch — which only writes a timestamp — never ran.This is hard to notice because three conditions stack: the hook always exits 0 (correct, per the hook contract), every internal call ends in
2>/dev/null, and in our install nothing actually readagent-status.json, so the empty file never looked wrong.Three defects, each silent on its own
The event source does not exist. Fixed by reading
argv[1]— the same pattern the plugin dispatcher already uses insettings.json— with a fallback to thehook_event_namefield of the stdin payload.The payload parser could not match the real payload. It used
grep -oanchored on"tool_name":"— no space after the colon — while the actual payload is pretty-printed ("tool_name": "Agent"). Replaced with a json parse. This one was hidden behind the first: fixing only the env var would have left the hook silent.$DESCRIPTIONwas interpolated into apython3 -cstring, so a quote or apostrophe in the description broke the script — and2>/dev/nullswallowed it. Values now go through argv.One behavior addition
Adds a
PostToolUsebranch. The hook previously only appended on start and cleared everything onStop, soactive_agentscould never reflect what is running — only what had ever started. Since the stated purpose is to "show which agents are running in real-time", entries need to leave the list when an agent finishes.Registering it requires one entry in
settings.json:{ "matcher": "Agent", "hooks": [{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/agent-tracker.sh\" PostToolUse" }] }The fallback means existing registrations keep working unchanged — the event is then taken from the payload.
Verification
Tested against: the real pretty-printed payload; the payload-only fallback (no argv); a non-
Agenttool (must not record); malformed input (still exits 0, per contract); andStop.Control: ran the previous version with the same real payload — it writes nothing, leaving
last_updatedempty. That is the current behavior onmain.Found while auditing multi-agent orchestration in a downstream install. The same defect is present in any EvoNexus install with this hook registered.
Summary by Sourcery
Fix the agent activity tracking hook so it correctly records agent lifecycle events and keeps the status file in sync with currently running agents.
New Features:
Bug Fixes:
Enhancements: