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:
@@ -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 Tool Hooks
|
||||||
|
|
||||||
MCP tools follow the pattern `mcp__<server>__<tool>`:
|
MCP tools follow the pattern `mcp__<server>__<tool>`:
|
||||||
|
|||||||
18
openspec/changes/add-context-usage-hook-example/proposal.md
Normal file
18
openspec/changes/add-context-usage-hook-example/proposal.md
Normal 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
|
||||||
@@ -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
|
||||||
9
openspec/changes/add-context-usage-hook-example/tasks.md
Normal file
9
openspec/changes/add-context-usage-hook-example/tasks.md
Normal 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
|
||||||
Reference in New Issue
Block a user