Stop Claude Code from re-reading files it already has in context.
A PreToolUse hook that tracks file reads within a session. When Claude tries to re-read a file that hasn't changed, the hook tells Claude the content is already in context. Saves ~2000+ tokens per prevented re-read.
By default, read-once uses warn mode: it allows the read but attaches an advisory message. This prevents the Edit tool deadlock (Edit requires a prior Read) and parallel read cascade failures. Set READ_ONCE_MODE=deny for hard blocking if you want maximum token savings and don't use Edit frequently.
One command:
curl -fsSL https://raw.githubusercontent.com/Bande-a-Bonnot/Boucle-framework/main/tools/read-once/install.sh | bashThis downloads hook.sh, compact.sh, and read-once to ~/.claude/read-once/ and adds both hooks to your settings.
Or clone and install manually:
git clone https://github.com/Bande-a-Bonnot/Boucle-framework.git
cd Boucle-framework/tools/read-once
./read-once installirm https://raw.githubusercontent.com/Bande-a-Bonnot/Boucle-framework/main/tools/install.ps1 | iexOr clone and install manually:
git clone https://github.com/Bande-a-Bonnot/Boucle-framework.git
cd Boucle-framework/tools/read-once
pwsh read-once.ps1 installRequires PowerShell 7+ (pwsh), not the built-in Windows PowerShell 5.1. Install with winget install Microsoft.PowerShell if needed.
Add to .claude/settings.json by hand:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Read",
"hooks": [
{
"type": "command",
"command": "/path/to/read-once/hook.sh"
}
]
}
],
"PostCompact": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "/path/to/read-once/compact.sh"
}
]
}
]
}
}On Windows, use "command": "pwsh -File ~/.claude/read-once/hook.ps1" for PreToolUse and "command": "pwsh -File ~/.claude/read-once/compact.ps1" for PostCompact.
- Hook intercepts every
Readtool call - Partial reads (with
offsetorlimit) always pass through — only full-file reads are cached - Checks a session-scoped cache: has this file been read before?
- Compares file mtime — if unchanged, advises Claude the content is already in context
- In warn mode (default): allows the read with advisory. In deny mode: blocks the read entirely
- If the file changed since last read, allows it through (or shows just the diff — see below)
- Cache entries expire after 20 minutes (configurable) to handle context compaction
When you're iterating on a file — read it, edit it, read it again — Claude already has the old version in context. With diff mode enabled, read-once shows only what changed instead of the full file. A 3-line change in a 200-line file costs ~30 tokens instead of ~2000.
Enable with:
export READ_ONCE_DIFF=1When a re-read is blocked with a diff, Claude sees:
read-once: app.py changed since last read. You already have the previous
version in context. Here are only the changes (saving ~1850 tokens):
--- previous
+++ current
@@ -45,3 +45,3 @@
- return None
+ return default_value
Apply this diff mentally to your cached version of the file.
If the diff is too large (>40 lines by default), read-once falls back to allowing a full re-read. Configure the threshold with READ_ONCE_DIFF_MAX.
When a re-read is blocked, Claude receives:
read-once: schema.rb (~2,340 tokens) already in context (read 3m ago, unchanged).
Re-read allowed after 20m. Session savings: ~4,680 tokens.
Claude then proceeds without the redundant read. No loss of information — the file content is still in the context window from the first read.
Claude Code compacts the context window during long sessions, dropping older content. A file read 30 minutes ago might no longer be in the working context.
read-once handles this two ways:
-
PostCompact hook (recommended):
compact.shregisters as a PostCompact hook and clears the session cache immediately when compaction occurs. The installer configures this automatically. -
TTL fallback: Cache entries also expire after
READ_ONCE_TTLseconds (default: 1200 = 20 minutes). This catches cases where PostCompact is not configured.
You can also manually reset the cache:
./read-once clear./read-once stats # macOS/Linux
pwsh read-once.ps1 stats # Windowsread-once — file read deduplication for Claude Code
Total file reads: 47
Cache hits: 19 (blocked re-reads)
Diff hits: 3 (changed files — sent diff only)
First reads: 22
Changed files: 1 (full re-read after modification)
TTL expired: 2 (re-read after 20m — compaction safety)
Tokens saved: ~38400
Read token total: ~94200
Savings: 40%
Est. cost saved: $0.1152 (Sonnet) / $0.5760 (Opus)
Top re-read files:
5x schema.rb
4x routes.rb
3x application_controller.rb
Sessions tracked: 3
Cache TTL: 20 minutes (READ_ONCE_TTL=1200s)
read-once stats Show token savings
read-once gain Same as stats
read-once verify Full diagnostic with dry-run test
read-once status Quick health check
read-once clear Clear session cache
read-once install Add hook to settings
read-once upgrade Update installed hook to latest
read-once uninstall Remove hook
On Windows, use pwsh read-once.ps1 <command> instead of ./read-once <command>.
After installing, run read-once verify to confirm everything works:
$ ./read-once verify
read-once verify
Dependencies:
[ok] jq found (jq-1.7.1)
[ok] bash 5.2.37 (4+ required)
[ok] python3 found (needed for diff mode)
[ok] stat found
Installation:
[ok] Hook file exists at ~/.claude/read-once/hook.sh
[ok] Hook file is executable
[ok] Installed hook matches source (up to date)
[ok] ~/.claude/settings.json exists
[ok] settings.json is valid JSON
[ok] PreToolUse Read matcher configured
[ok] Hook command path resolves (~/.claude/read-once/hook.sh)
Dry-run test:
[ok] First read: allowed (no output = pass-through)
[ok] Second read: produced valid JSON response
[ok] Second read: correctly detected re-read (mode: warn)
Configuration:
Mode: warn (READ_ONCE_MODE)
TTL: 1200s (20m) (READ_ONCE_TTL)
Diff: 0 (READ_ONCE_DIFF)
Disabled: 0 (READ_ONCE_DISABLED)
13/13 checks passed. read-once is ready.
If any check fails, verify tells you exactly what to fix.
Environment variables:
| Variable | Default | Description |
|---|---|---|
READ_ONCE_MODE |
warn |
warn allows reads with advisory message. deny blocks reads entirely. Warn mode prevents Edit tool deadlock and parallel read cascade failures. |
READ_ONCE_TTL |
1200 |
Cache TTL in seconds. After this, re-reads are allowed (compaction safety). |
READ_ONCE_DIFF |
0 |
Set to 1 to show only diffs when files change (instead of full re-read). |
READ_ONCE_DIFF_MAX |
40 |
Max diff lines before falling back to full re-read. |
READ_ONCE_DISABLED |
0 |
Set to 1 to disable the hook entirely. |
macOS / Linux:
jq(for JSON parsing)bash4+python3(optional, for diff mode JSON escaping)- Claude Code with hooks support
Windows:
- PowerShell 7+ (
pwsh), not the built-in Windows PowerShell 5.1 - Claude Code with hooks support
Claude Code re-reads files more than you'd think. Common patterns:
- Reading a file, editing it, then reading it again to verify
- Re-reading config files across different parts of a task
- Reading the same file in subagents that share a session
Each blocked re-read saves the full file token cost (including the ~70% overhead from line numbers in cat -n format). Run ./read-once stats after a session to see your actual savings.
The Edit tool says "File has not been read yet" even though I already read it.
This happens in deny mode (READ_ONCE_MODE=deny). Claude Code's Edit tool requires a successful Read before it will edit a file. When read-once blocks the Read, Edit thinks the file was never read. The fix: use the default warn mode (READ_ONCE_MODE=warn), which allows reads with an advisory instead of blocking them.
Won't this break after context compaction?
The installer configures a PostCompact hook (compact.sh) that clears the cache immediately when compaction happens. As a fallback, cache entries also expire after 20 minutes (configurable via READ_ONCE_TTL). You can also run read-once clear to reset manually.
Claude Code reads small chunks, not whole files — does this help?
Partial reads with offset or limit are never cached. They always pass through. read-once only deduplicates full-file reads where the entire file is requested again.
Isn't there a good reason Claude re-reads files?
Yes — when the file changed, or when context compacted. read-once only blocks re-reads when the file hasn't changed (same mtime) and the cache is recent. Changed files always pass through. With diff mode enabled (READ_ONCE_DIFF=1), changed files show just the delta instead of the full content.
Works alongside RTK (which handles Bash output) and Context-Mode (which handles large outputs). read-once operates on a different layer — the Read tool — so there's no conflict.
MIT
