How Tool Activity Tracking Works in Claude HUD: Extracting tool_use and tool_result Blocks from Transcript JSONL
Claude HUD implements tool activity tracking by streaming the session transcript as line-delimited JSON, detecting tool_use blocks to create running tool entries, and matching subsequent tool_result blocks by ID to update completion status.
The jarrodwatts/claude-hud repository provides a terminal heads-up display for Claude Code sessions. At its core, the tool activity tracking system parses the live transcript file to extract every tool invocation and its outcome. This functionality resides primarily in src/transcript.ts, which processes the JSONL stream and maintains state maps for active tools.
Parsing the Transcript JSONL Stream
Streaming Line-by-Line with parseTranscript
The entry point for tool activity tracking is the parseTranscript function in src/transcript.ts. Rather than loading the entire transcript into memory, it opens the file as a read stream and processes it line-by-line using Node.js readline.
This approach is critical for performance, as Claude Code transcripts can grow large during extended sessions. The function iterates through each line, parses valid JSON into a TranscriptLine object, and passes it to processEntry for content analysis.
The TranscriptLine Structure
Each line in the transcript represents a message or event in the Claude session. The TranscriptLine type contains a content array where individual blocks—text, tool uses, and tool results—are stored. The parser examines each block's type property to determine how to handle it.
Detecting and Tracking Tool Invocations
Identifying tool_use Blocks
Inside processEntry, the code walks every block in the content array. When it encounters a block where block.type === 'tool_use', it validates that the block contains an id and name property. These fields are essential for tracking, as the id serves as the unique key for matching results later.
Creating ToolEntry Objects
Upon detecting a valid tool_use block, the system creates a ToolEntry object with the following properties:
id: The tool invocation ID from the blockname: The tool name (e.g., "Read", "Write", "Task")status: Set to'running'immediately upon creationstartTime: Timestamp from the current transcript line
This object captures the state of the tool at the moment of invocation.
Storing Active Tools in toolMap
The ToolEntry is stored in a Map<string, ToolEntry> called toolMap, keyed by the tool's id. This map maintains the state of all currently running tools, allowing the system to update them when results arrive. The use of a Map ensures O(1) lookups when matching results to invocations.
Matching Tool Results to Running Tools
Processing tool_result Blocks
When processEntry encounters a block with type === 'tool_result', it extracts the tool_use_id property. This ID corresponds to the id assigned to the tool_use block that initiated the tool call. The system looks up this ID in toolMap to find the matching ToolEntry.
Updating Status and Completion Time
If a matching entry exists, the system updates the ToolEntry with the result information:
status: Changed to'completed'if successful, or'error'if the result block hasis_error: trueendTime: Set to the timestamp of the current transcript line
This update completes the lifecycle of the tool invocation, capturing both the start and end times for duration calculation.
Handling Agent-Specific Task Tools
The same mechanism applies to agent tracking via agentMap. When the Task tool (used for sub-agent delegation) is invoked, it creates an AgentEntry in agentMap. The subsequent tool_result for that Task updates the agent's status to 'completed' using the same ID-matching logic.
Rendering the Tool Activity Line
After processing the transcript, the parseTranscript function converts the toolMap and agentMap to arrays and truncates them to the most recent entries (last 20 tools, last 10 agents). This data is passed to src/render/tools-line.ts, which formats the tool activity into a concise status line showing running and recently completed tools.
Code Examples
Parsing and Filtering Running Tools
This example demonstrates how to use the parseTranscript function to extract currently running tools from a Claude session transcript:
import { parseTranscript } from './transcript.js';
async function showRunningTools(path: string) {
const data = await parseTranscript(path);
console.log('Running tools:');
data.tools
.filter(t => t.status === 'running')
.forEach(t => {
console.log(`- ${t.name} (${t.id}) started at ${t.startTime.toISOString()}`);
});
}
// Example usage (the path is supplied by Claude HUD at runtime)
showRunningTools('/home/user/.claude/transcript.jsonl');
Rendering the Status Line
This simplified example shows how the parsed tool data flows into the renderer:
import { renderToolsLine } from './render/tools-line.js';
import { parseTranscript } from './transcript.js';
export async function renderStatus(transcriptPath: string) {
const transcript = await parseTranscript(transcriptPath);
const line = renderToolsLine(transcript.tools);
process.stdout.write(line + '\n');
}
Summary
- Streaming parser:
parseTranscriptinsrc/transcript.tsprocesses the JSONL transcript line-by-line to minimize memory usage. - Block detection: The system identifies
tool_useblocks to createToolEntryobjects withstatus: 'running'and stores them in atoolMapkeyed by tool ID. - Result matching: Incoming
tool_resultblocks are matched to running tools viatool_use_id, updating status to'completed'or'error'and recordingendTime. - Agent support: The same pattern tracks sub-agent
Tasktools usingagentMap. - Rendering: Results are truncated to recent entries and formatted by
src/render/tools-line.tsfor display.
Frequently Asked Questions
What file format does Claude HUD use for transcripts?
Claude HUD reads the Claude Code session transcript as a line-delimited JSON (JSONL) file. Each line represents a discrete event or message in the conversation, containing content blocks that may include text, tool invocations, or tool results. The parseTranscript function treats this as a read stream to handle large files efficiently.
How does the tool tracker handle errors in tool execution?
When processing a tool_result block, the code checks the is_error property. If is_error is true, the matching ToolEntry in toolMap has its status set to 'error' instead of 'completed'. This allows the renderer to display failed tool invocations distinctly from successful ones, providing immediate visual feedback about execution problems.
Can I use the parseTranscript function outside of Claude HUD?
Yes, the parseTranscript function is designed as a standalone utility that accepts a file path string and returns a TranscriptData object containing arrays of tools and agents. You can import it into other TypeScript projects to analyze Claude Code transcripts programmatically, filter for specific tool usage patterns, or build custom analytics dashboards.
Where does the tool activity information get displayed?
After parsing, the tool activity data flows to src/render/tools-line.ts, which formats the information into a concise status line. This line displays currently running tools and recently completed ones directly in the terminal HUD, giving users real-time visibility into Claude Code's tool usage without interrupting their workflow.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →