Architecture
How Claude HUD works under the hood — from data ingestion to terminal rendering.
Prerequisites: Familiarity with Claude HUD overview and HUD elements.
High-Level Data Flow
Claude Code invokes the HUD plugin as a child process roughly every 300 ms. Each invocation is a fresh Node.js process that reads data, renders output, and exits. There is no long-running daemon or persistent state in memory.
The pipeline follows five stages:
- Stdin ingestion — Claude Code pipes a JSON payload containing model info, context window metrics, and a transcript file path.
- Transcript parsing — The plugin reads the session transcript (a JSONL file on disk) to extract tool calls, agent tasks, and todo items.
- Config and environment collection — CLAUDE.md files, MCP servers, hooks, rules, git status, and OAuth usage data are gathered from the filesystem and APIs.
- Rendering — Each HUD element is rendered into ANSI-colored text lines.
- Output — Lines are printed to stdout, where Claude Code displays them as the statusline.

Data Sources
Claude HUD combines three categories of input data. Understanding these sources is essential for debugging unexpected display values or extending the plugin with new indicators.
Stdin JSON
Claude Code writes a JSON object to the plugin’s stdin on every invocation. This is the primary source of accurate, real-time session data.
interface StdinData {
transcript_path?: string; // Path to session transcript JSONL
cwd?: string; // Current working directory
model?: {
id?: string; // Raw model ID (e.g., "anthropic.claude-sonnet-4-20250514")
display_name?: string; // Human-readable name (e.g., "Sonnet")
};
context_window?: {
context_window_size?: number; // Maximum context size in tokens
current_usage?: {
input_tokens?: number;
cache_creation_input_tokens?: number;
cache_read_input_tokens?: number;
};
used_percentage?: number; // Native percentage (v2.1.6+)
};
}
The used_percentage field was introduced in Claude Code v2.1.6. When available, the HUD uses it directly instead of computing tokens manually — this ensures the displayed percentage matches what Claude Code reports in /context.
Transcript JSONL
The transcript file is a newline-delimited JSON log of every message in the current session. The HUD parses it line by line using a streaming readline interface, extracting:
| Block Type | Data Extracted | HUD Element |
|---|---|---|
tool_use |
Tool name, target file, start time | Tools line |
tool_result |
Completion status, error flag | Tools line (status update) |
Task tool_use |
Agent type, model, description | Agents line |
TodoWrite tool_use |
Full todo list | Todos line |
TaskCreate / TaskUpdate |
Individual task status changes | Todos line |
A tool is considered “running” when a tool_use block has no matching tool_result block (matched by ID). This is how the HUD shows spinner indicators for in-progress operations.
Configuration Files
The plugin scans multiple locations to count configuration items displayed in the environment line:
| Source | What It Counts |
|---|---|
~/.claude/CLAUDE.md |
CLAUDE.md files (user scope) |
{cwd}/CLAUDE.md, {cwd}/CLAUDE.local.md |
CLAUDE.md files (project scope) |
~/.claude/settings.json |
MCP servers, hooks |
{cwd}/.mcp.json |
Project MCP servers |
{cwd}/.claude/settings.json |
Project MCP servers, hooks |
~/.claude/rules/*.md |
Rule files (recursive) |
Disabled MCP servers (listed in disabledMcpServers or disabledMcpjsonServers) are subtracted from the count. The plugin also avoids double-counting when the project .claude directory overlaps with the user-scope directory.
Entry Point and Orchestration
The main() function in src/index.ts orchestrates the entire pipeline. It uses dependency injection for testability — every external dependency is passed through a MainDeps object that can be overridden in tests.
export async function main(overrides: Partial<MainDeps> = {}): Promise<void> {
const deps: MainDeps = {
readStdin, parseTranscript, countConfigs,
getGitStatus, getUsage, loadConfig,
parseExtraCmdArg, runExtraCmd, render,
now: () => Date.now(),
log: console.log,
...overrides,
};
const stdin = await deps.readStdin();
if (!stdin) {
deps.log('[claude-hud] Initializing...');
return;
}
// ... orchestration continues
}
When no stdin data is available (e.g., during first invocation or when running interactively), the plugin outputs an initialization message and exits gracefully.
The orchestration sequence:
- Read and parse stdin JSON
- Parse the transcript file from the path in stdin
- Count configuration items from the filesystem
- Load user configuration (
config.json) - Optionally fetch git status and OAuth usage data
- Build a
RenderContextobject combining all collected data - Pass the context to the render pipeline
Rendering Pipeline
The render system is organized as a set of independent line renderers coordinated by src/render/index.ts. Each renderer receives the full RenderContext and returns either a formatted string or null (when there is nothing to display).
Layout Modes
The plugin supports two layout modes controlled by the lineLayout configuration:
- Expanded (default) — Each HUD element gets its own line. The element order is configurable via
elementOrder. Adjacentcontextandusageelements are merged onto a single line separated by│. - Compact — Combines the session information into a single dense line. Activity indicators (tools, agents, todos) appear below.
Element Renderers
| Element | Renderer File | Always Shown |
|---|---|---|
| Project | src/render/lines/project-line.ts |
Yes |
| Context | src/render/lines/identity-line.ts |
Yes |
| Usage | src/render/lines/usage-line.ts |
When data available |
| Environment | src/render/lines/environment-line.ts |
When thresholds met |
| Tools | src/render/tools-line.ts |
When tools used |
| Agents | src/render/agents-line.ts |
When agents active |
| Todos | src/render/todos-line.ts |
When todos exist |
Terminal Width Handling
The render pipeline detects terminal width from process.stdout.columns (falling back to the COLUMNS environment variable). Lines that exceed the terminal width are wrapped at separator boundaries (| or │) rather than mid-word. If a single segment still exceeds the width, it is truncated with an ellipsis.
The width calculation accounts for ANSI escape sequences (which occupy zero visual width), emoji glyphs (which occupy two columns), and Unicode combining characters.
Color System
Colors are applied through ANSI escape codes via helper functions in src/render/colors.ts. Context usage thresholds determine the bar color:
| Range | Color | Meaning |
|---|---|---|
| 0–70% | Green | Healthy — plenty of context remaining |
| 70–85% | Yellow | Warning — consider wrapping up or compacting |
| 85%+ | Red | Critical — shows token breakdown for diagnosis |
All color names are configurable through the colors field in config.json, supporting: red, green, yellow, magenta, cyan, brightBlue, and brightMagenta.
Usage API Integration
The HUD optionally fetches plan usage data from the Anthropic OAuth API (api.anthropic.com/api/oauth/usage). Because the plugin runs as a new process every ~300 ms, a file-based caching layer prevents excessive API calls.
Cache Strategy
~/.claude/plugins/claude-hud/.usage-cache.json
| Scenario | Cache TTL |
|---|---|
| Successful response | 60 seconds (configurable) |
| Failed request | 15 seconds (configurable) |
| Rate limited (429) | Exponential backoff: 60s, 120s, 240s, up to 5 min |
A file-based lock (.usage-cache.lock) prevents concurrent API calls when multiple HUD instances run simultaneously. If the lock is held, the process waits up to 2 seconds for fresh cache data before falling back to stale data.
Credential Resolution
On macOS, the plugin reads OAuth tokens from the system Keychain using /usr/bin/security. On other platforms (or when Keychain is unavailable), it falls back to ~/.claude/.credentials.json. The credential lookup includes:
- Try macOS Keychain with profile-specific service name
- Try macOS Keychain with legacy service name
- Fall back to file-based credentials
- If all fail, usage display is silently disabled
A backoff mechanism prevents repeated Keychain prompts — after a failure, the plugin skips Keychain access for 60 seconds.
File Structure
src/
├── index.ts # Entry point, orchestrates the pipeline
├── stdin.ts # Reads and parses stdin JSON
├── transcript.ts # Parses transcript JSONL for tools/agents/todos
├── config.ts # Loads and validates user configuration
├── config-reader.ts # Counts CLAUDE.md, MCP, hooks, rules
├── git.ts # Git branch, dirty state, ahead/behind
├── usage-api.ts # OAuth usage API with file-based caching
├── types.ts # Shared TypeScript interfaces
├── constants.ts # Shared constants
├── render/
│ ├── index.ts # Render coordinator (layout, wrapping, output)
│ ├── session-line.ts # Compact mode session line
│ ├── tools-line.ts # Tool activity with spinners
│ ├── agents-line.ts # Agent status with elapsed time
│ ├── todos-line.ts # Todo progress
│ ├── colors.ts # ANSI color helpers
│ └── lines/
│ ├── identity-line.ts # Model and context bar
│ ├── project-line.ts # Project name and git info
│ ├── usage-line.ts # Plan usage bars
│ └── environment-line.ts # Config counts
Extension Points
Claude HUD is designed to be extended with new data sources and display elements.
Adding a New HUD Line
- Create a new renderer in
src/render/that exports a function acceptingRenderContextand returningstring | null. - Add any new data fields to the
RenderContextinterface insrc/types.ts. - If the data comes from the transcript, add extraction logic in
src/transcript.ts. - Register the renderer in
src/render/index.ts. - Add the new element to the
HudElementtype insrc/config.tsand include it inDEFAULT_ELEMENT_ORDER.
Adding a New Data Source
- Create a new module in
src/with an async function that returns the data. - Add the function to the
MainDepsinterface insrc/index.ts. - Call it from
main()and add the result toRenderContext. - Reference the data in the appropriate renderer.
Modifying Thresholds
Context color thresholds are defined in the session line renderer (src/render/session-line.ts). The percentage checks that determine color coding can be adjusted there. Usage display thresholds are configurable via config.json without code changes.
Related Topics
- Overview — What Claude HUD shows and why it matters
- HUD Elements — Detailed guide to every display element
- Configuration — All configuration options and presets
- Troubleshooting — Fixing common issues