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 block
  • name: The tool name (e.g., "Read", "Write", "Task")
  • status: Set to 'running' immediately upon creation
  • startTime: 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 has is_error: true
  • endTime: 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: parseTranscript in src/transcript.ts processes the JSONL transcript line-by-line to minimize memory usage.
  • Block detection: The system identifies tool_use blocks to create ToolEntry objects with status: 'running' and stores them in a toolMap keyed by tool ID.
  • Result matching: Incoming tool_result blocks are matched to running tools via tool_use_id, updating status to 'completed' or 'error' and recording endTime.
  • Agent support: The same pattern tracks sub-agent Task tools using agentMap.
  • Rendering: Results are truncated to recent entries and formatted by src/render/tools-line.ts for 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →