docs: Add context usage reporter hook example

Add Example 6 showing how to create a hook that reports context/token
usage after each Claude response:

- Python script reads transcript_path to access conversation history
- Estimates tokens using ~4 chars/token heuristic
- Outputs one-line report: "Context: ~45k/200k tokens (77% remaining)"
- Documents both Stop and UserPromptSubmit hook configurations
- Explains limitations (estimate vs exact /context command)
This commit is contained in:
Luong NGUYEN
2025-12-24 23:21:38 +01:00
parent e144e42b20
commit 9c5c7c5049
4 changed files with 230 additions and 0 deletions

View File

@@ -480,6 +480,191 @@ if __name__ == "__main__":
}
```
### Example 6: Context Usage Reporter (Stop Hook)
This example shows how to create a hook that reports context/token usage after each Claude response. It reads the conversation transcript and estimates token usage.
**How it works:**
1. The hook receives `transcript_path` in the JSON input - this points to a JSONL file containing all conversation messages
2. The script reads the transcript file and calculates total character count
3. It estimates tokens using a simple heuristic (~4 characters per token)
4. Outputs a one-line report showing estimated usage vs model capacity
**File:** `.claude/hooks/context-usage.py`
```python
#!/usr/bin/env python3
"""
Context Usage Reporter Hook
Reports estimated context/token usage after each Claude response.
Uses the transcript_path field to read conversation history and estimate tokens.
Limitations:
- Token count is an ESTIMATE (~4 chars/token average)
- Actual token usage depends on the tokenizer and includes system prompts
- Use /context command for accurate real-time usage
"""
import json
import sys
import os
# Model context limits (adjust based on your model)
MODEL_LIMITS = {
"default": 200000, # Claude Opus 4.5 / Sonnet
"haiku": 200000,
}
def estimate_tokens(text: str) -> int:
"""Estimate token count from text. ~4 characters per token on average."""
return len(text) // 4
def read_transcript(transcript_path: str) -> list:
"""Read JSONL transcript file and return list of messages."""
messages = []
if not os.path.exists(transcript_path):
return messages
with open(transcript_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line:
try:
messages.append(json.loads(line))
except json.JSONDecodeError:
continue
return messages
def calculate_usage(messages: list) -> tuple[int, int]:
"""Calculate total characters and estimated tokens from messages."""
total_chars = 0
for msg in messages:
# Handle different message formats in transcript
if isinstance(msg, dict):
# Check common content fields
content = msg.get('content', '')
if isinstance(content, str):
total_chars += len(content)
elif isinstance(content, list):
# Handle content blocks (text, tool_use, etc.)
for block in content:
if isinstance(block, dict):
text = block.get('text', '') or block.get('content', '')
total_chars += len(str(text))
elif isinstance(block, str):
total_chars += len(block)
# Also count tool inputs/outputs
tool_input = msg.get('tool_input', {})
if tool_input:
total_chars += len(json.dumps(tool_input))
estimated_tokens = estimate_tokens(str(total_chars))
return total_chars, estimated_tokens
def main():
# Read hook input from stdin
input_data = json.load(sys.stdin)
# Get transcript path from hook input
transcript_path = input_data.get('transcript_path', '')
if not transcript_path:
# No transcript available, exit silently
sys.exit(0)
# Read and analyze transcript
messages = read_transcript(transcript_path)
total_chars, estimated_tokens = calculate_usage(messages)
# Get model limit (default to 200k)
max_tokens = MODEL_LIMITS.get("default", 200000)
# Calculate percentages
used_percent = (estimated_tokens / max_tokens) * 100
remaining_tokens = max_tokens - estimated_tokens
remaining_percent = 100 - used_percent
# Format the report (output as systemMessage so it appears in UI)
report = f"Context: ~{estimated_tokens:,}/{max_tokens:,} tokens ({remaining_percent:.1f}% remaining)"
# Output JSON with systemMessage to show in Claude Code UI
output = {
"systemMessage": report
}
print(json.dumps(output))
sys.exit(0)
if __name__ == "__main__":
main()
```
**Configuration:**
```json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-usage.py\"",
"timeout": 5
}
]
}
]
}
}
```
**Sample Output:**
After each Claude response, you'll see a message like:
```
Context: ~45,230/200,000 tokens (77.4% remaining)
```
**Key Points:**
| Aspect | Details |
|--------|---------|
| **Event** | `Stop` - runs after Claude finishes responding |
| **Input** | Uses `transcript_path` field to access conversation history |
| **Estimation** | ~4 characters per token (rough heuristic) |
| **Output** | `systemMessage` field displays in Claude Code UI |
| **Accuracy** | Estimate only - use `/context` for exact counts |
**Why use Stop hook instead of UserPromptSubmit?**
- `Stop` runs after Claude responds, giving a more complete picture
- `UserPromptSubmit` runs before Claude processes, missing the response
- Both work, but `Stop` shows total usage including Claude's response
**Alternative: UserPromptSubmit for Pre-Response Check**
If you want to check context BEFORE Claude processes your prompt:
```json
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/context-usage.py\""
}
]
}
]
}
}
```
## MCP Tool Hooks
MCP tools follow the pattern `mcp__<server>__<tool>`:

View File

@@ -0,0 +1,18 @@
# Change: Add Context Usage Reporting Hook Example
## Why
Users want to monitor their context window usage during Claude Code sessions. The hooks documentation currently lacks a practical example showing how to create a hook that reports context/token usage after each user request. This is valuable for understanding when context is getting full and when auto-compaction might occur.
## What Changes
- Add a detailed example hook that reports context usage after each user prompt
- The hook will read the transcript file and estimate token usage
- Include step-by-step explanation of how the hook works
- Document the limitations (estimation vs exact token counts)
## Impact
- **Affected specs**: hooks-documentation (add new example)
- **Affected code**: `06-hooks/README.md` (add new example section)
- **User impact**: Users gain a practical example for monitoring context usage

View File

@@ -0,0 +1,18 @@
# Hooks Documentation Specification
## ADDED Requirements
### Requirement: Context Usage Reporting Hook Example
The hooks lesson SHALL include a detailed example showing how to create a hook that reports context/token usage after each user request.
#### Scenario: User learns to create context monitoring hook
- **WHEN** a user reads the context usage reporter example
- **THEN** they find a complete Python script that reads the transcript file
- **AND** they understand how to estimate token usage from conversation history
- **AND** they see the configuration for UserPromptSubmit or Stop hooks
- **AND** they understand the limitations of token estimation
#### Scenario: Hook output format is documented
- **WHEN** a user implements the context usage hook
- **THEN** they can generate a one-line report showing used tokens and remaining capacity
- **AND** they understand the report is an estimate based on transcript analysis

View File

@@ -0,0 +1,9 @@
# Tasks: Add Context Usage Reporting Hook Example
## 1. Documentation Update
- [x] 1.1 Add new example section "Context Usage Reporter" to 06-hooks/README.md
- [x] 1.2 Write Python hook script that reads transcript and estimates tokens
- [x] 1.3 Add configuration example for UserPromptSubmit hook
- [x] 1.4 Document how transcript_path provides access to conversation history
- [x] 1.5 Explain token estimation approach and limitations
- [x] 1.6 Show sample output format for the one-line report