# Tracking Context and Token Usage with Claude Code Hooks

> Track Claude token usage and context effortlessly with code hooks. Intercept events, calculate deltas, and output stats without workflow disruption. Learn how.

- Repository: [Luong NGUYEN/claude-howto](https://github.com/luongnv89/claude-howto)
- Tags: how-to-guide
- Published: 2026-03-30

---

**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`](https://github.com/luongnv89/claude-howto/blob/main/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`](https://github.com/luongnv89/claude-howto/blob/main/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`](https://github.com/luongnv89/claude-howto/blob/main/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`](https://github.com/luongnv89/claude-howto/blob/main/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:

```bash
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:

```bash
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:

```json
{
  "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`](https://github.com/luongnv89/claude-howto/blob/main/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:

```bash
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:

```python
#!/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`](https://github.com/luongnv89/claude-howto/blob/main/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`](https://github.com/luongnv89/claude-howto/blob/main/context-tracker.py) for zero-dependency operation or [`context-tracker-tiktoken.py`](https://github.com/luongnv89/claude-howto/blob/main/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`](https://github.com/luongnv89/claude-howto/blob/main/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`](https://github.com/luongnv89/claude-howto/blob/main/.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.