Hook transcript: tool results indistinguishable from real user messages
Problem
When writing hooks that need to measure how long Claude worked on a turn (e.g., to only play a notification sound for long-running tasks), the transcript JSONL makes it difficult to find the user's actual message.
Tool results are logged with type: "user" and userType: "external" — identical to real user-typed messages. The only way to distinguish them is checking for the presence of toolUseResult or sourceToolAssistantUUID fields:
// Real user message
{"type":"user","userType":"external","timestamp":"2026-02-18T06:45:16.545Z","message":{...}}
// Tool result (looks the same at first glance)
{"type":"user","userType":"external","timestamp":"2026-02-18T06:45:24.221Z","toolUseResult":{...},"sourceToolAssistantUUID":"..."}
Impact
A hook that measures elapsed time since the last user message needs to use a workaround like:
tac "$TRANSCRIPT" | jq -r 'select(.type=="user" and .userType=="external" and (has("toolUseResult") | not)) | .timestamp // empty' | head -1
This is fragile and non-obvious. Without knowing to check has("toolUseResult"), the naive filter select(.type=="user" and .userType=="external") picks up the most recent tool result instead of the user's message, making the elapsed time always appear as a few seconds.
Suggestion
Any of these would help:
- Use a distinct
userTypefor tool results (e.g.,"tool_result"instead of"external") - Add a
turn_start_timestampfield to the Stop/Notification hook event JSON, so hooks don't need to parse the transcript at all - Add an
elapsed_secondsorturn_durationfield directly to the Stop hook event
Option 2 or 3 would be ideal — it would make duration-based hooks trivial without needing transcript parsing at all.
Use case
Playing an audible notification only when Claude has been working for 15+ seconds (so quick answers stay silent). This requires knowing when the user's turn started, which currently requires parsing the transcript and filtering out tool results.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗