Tracking Context and Token Usage with Claude Code Hooks

Claude Code hooks intercept UserPromptSubmit and Stop events to calculate token deltas by comparing pre-request and post-response transcript states, outputting usage statistics to stderr without disrupting your workflow.

Claude Code supports event-driven hooks that execute scripts when specific session events fire. According to the luongnv89/claude-howto repository, you can leverage the UserPromptSubmit and Stop hooks to build a persistent context tracker that monitors how much of Claude's 128,000-token window you consume with every request. This article explains the implementation details found in the repository's hook examples and provides the exact configuration needed to deploy the tracker.

How the Context Usage Tracker Works

The tracker operates as a stateful pair of scripts that sandwich each Claude request. It uses a temporary JSON state file to persist token counts between the pre-message and post-response phases.

The UserPromptSubmit Hook (Pre-Request)

When you submit a prompt, Claude Code fires the UserPromptSubmit event and passes a JSON payload containing transcript_path. The script's handle_user_prompt_submit function (defined in 06-hooks/context-tracker.py, lines 65-78) reads the current transcript file and estimates the token count using either a character-based heuristic or the tiktoken library. It then writes this baseline count to a session-specific state file in /tmp/claude-context-<session_id>.json.

The Stop Hook (Post-Response)

After Claude finishes generating a response, the Stop event triggers the handle_stop function (lines 79-111 in context-tracker.py). This function re-reads the updated transcript, recalculates the total token count, and loads the previous count from the state file. It calculates the delta (tokens consumed by the request) and prints a human-readable report to stderr, ensuring the output does not interfere with tool results or piped commands.

State Management and Isolation

The get_state_file function (lines 25-28) generates a unique state path using the session ID, guaranteeing that parallel Claude Code sessions do not overwrite each other's data. The state file persists only for the duration of the session and is automatically cleaned up by the OS temporary directory policies.

Token Estimation Strategies

The repository provides two estimation methods:

  • Zero-dependency estimation (count_tokens_estimate in context-tracker.py, lines 30-38): Uses len(text) // 4, assuming approximately four characters per token. This heuristic is sufficiently accurate for English prose and requires no external libraries.
  • High-accuracy estimation (count_tokens in context-tracker-tiktoken.py, lines 43-59): Uses the p50k_base encoding from the tiktoken library, which matches Claude's tokenizer with 90-95% accuracy. If tiktoken is not installed, the script gracefully falls back to the character heuristic.

Installation and Setup

Choose between the zero-dependency version for immediate use or the tiktoken version for precise accounting.

Option 1: Zero-Dependency Setup

Copy the standalone script to your Claude hooks directory:

mkdir -p ~/.claude/hooks
cp 06-hooks/context-tracker.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/context-tracker.py

Option 2: High-Accuracy Setup with Tiktoken

Install the tokenizer library first, then deploy the enhanced script:

pip install tiktoken
cp 06-hooks/context-tracker-tiktoken.py ~/.claude/hooks/
chmod +x ~/.claude/hooks/context-tracker-tiktoken.py

Configuring Hooks in settings.json

Register the tracker by adding hook definitions to ~/.claude/settings.json (or a project-specific settings file). Both scripts use identical configuration syntax:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "*",
        "hooks": [
          "~/.claude/hooks/context-tracker.py"
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          "~/.claude/hooks/context-tracker.py"
        ]
      }
    ]
  }
}

To use the tiktoken variant, simply change the path to context-tracker-tiktoken.py. The "matcher": "*" directive ensures the hooks run on every prompt regardless of content.

Usage Example and Output Format

After configuration, run any prompt to see the tracker in action:

claude "Explain the difference between Claude 2.1 and 2.2"

The tracker outputs to stderr:


Context (estimated): ~12,400 tokens (9.7% used, ~115,600 remaining)
This request: ~2,300 tokens

Because the report prints to stderr, it will not contaminate stdout when chaining commands or using Claude's output as input to other tools.

Extending the Tracker with Custom Alerts

You can extend the base functionality to warn when approaching the 128,000-token limit. Create a wrapper script that imports the original handlers and adds threshold checks:

#!/usr/bin/env python3
import sys
import json
import os

# Add the hooks directory to path to import the original module

sys.path.insert(0, os.path.expanduser('~/.claude/hooks'))
from context_tracker import handle_stop, handle_user_prompt_submit

CONTEXT_LIMIT = 128000
WARNING_THRESHOLD = 0.9

def main():
    data = json.load(sys.stdin)
    event = data.get("hook_event_name", "")
    
    if event == "UserPromptSubmit":
        handle_user_prompt_submit(data)
    elif event == "Stop":
        handle_stop(data)
        # Check final usage from the printed output or modify handle_stop to return values

        # For demonstration, re-calculate manually:

        transcript_path = data.get("transcript_path")
        if transcript_path:
            with open(transcript_path, 'r') as f:
                text = f.read()
            current = len(text) // 4
            if current > WARNING_THRESHOLD * CONTEXT_LIMIT:
                print(f"⚠️  Warning: Context usage at {current/CONTEXT_LIMIT:.1%}!", file=sys.stderr)

if __name__ == "__main__":
    main()

Save this as ~/.claude/hooks/context-tracker-alert.py, update the settings.json paths accordingly, and you will receive stderr alerts when usage exceeds 90%.

Summary

  • Hook events: The tracker uses UserPromptSubmit to capture baseline tokens and Stop to calculate consumption deltas.
  • State persistence: Session-isolated JSON files in /tmp store interim counts between events.
  • Estimation options: Use context-tracker.py for zero-dependency operation or context-tracker-tiktoken.py for 90-95% accurate token counts.
  • Configuration: Hook paths are registered in ~/.claude/settings.json under the "hooks" key with "matcher": "*" for universal execution.
  • Output: All reports print to stderr to avoid polluting command pipelines.

Frequently Asked Questions

How accurate is the character-based estimation?

The len(text) // 4 heuristic in context-tracker.py approximates English tokenization at roughly four characters per token. While it underestimates code and overestimates some languages, it typically stays within 10-15% of the true count for conversational text. For production workflows requiring precision, use the tiktoken variant which aligns with Claude's tokenizer at 90-95% accuracy.

Where is the session state stored?

The tracker writes temporary state files to /tmp/claude-context-<session_id>.json, where <session_id> is a unique identifier provided by Claude Code. The get_state_file function ensures parallel sessions maintain isolated state, preventing cross-contamination when running multiple Claude instances.

Can I use these hooks in project-specific settings instead of global?

Yes. While the examples reference ~/.claude/settings.json for global configuration, you can place the same JSON structure in a .claude/settings.json file within your project root. Claude Code merges project-specific settings with global ones, allowing per-repository token tracking without affecting other directories.

What happens if the temporary state file is deleted mid-session?

If the state file is deleted between the UserPromptSubmit and Stop events, the handle_stop function will fail to load the baseline count. In the provided implementation, this results in the script being unable to calculate the request-specific delta, though it will still report the total current context usage. To prevent this, the scripts rely on atomic file operations and the stability of the /tmp directory during a single session lifecycle.

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 →