claude-code - 💡(How to fix) Fix InstructionsLoaded hook fires 3x per file per compact event (causes 3x context reload waste) [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#52176Fetched 2026-04-23 07:34:35
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
labeled ×5

The InstructionsLoaded hook fires 3 times per instruction file per single user-experience compact event, with identical memory_type and load_reason per emission. Net effect: per-compact reload bytes-into-context is roughly 3× what naive accounting would suggest.

Root Cause

The InstructionsLoaded hook fires 3 times per instruction file per single user-experience compact event, with identical memory_type and load_reason per emission. Net effect: per-compact reload bytes-into-context is roughly 3× what naive accounting would suggest.

Code Example

# .claude/hooks/instructions-loaded-logger.py
import json, sys, time
payload = json.loads(sys.stdin.read())
with open("/tmp/il_log.jsonl", "a") as f:
    f.write(json.dumps({
        "ts": time.time(),
        "file_path": payload.get("file_path"),
        "load_reason": payload.get("load_reason"),
        "memory_type": payload.get("memory_type"),
    }) + "\n")
RAW_BUFFERClick to expand / collapse

Summary

The InstructionsLoaded hook fires 3 times per instruction file per single user-experience compact event, with identical memory_type and load_reason per emission. Net effect: per-compact reload bytes-into-context is roughly 3× what naive accounting would suggest.

Evidence (from one project's telemetry, ~280 sessions)

I built an InstructionsLoaded → DuckDB pipeline (ops.instructions_loaded table) following the documented file_path / memory_type / load_reason / bytes / globs / trigger_file_path / parent_file_path / cwd schema. After 1,000+ events captured, the pattern is:

For session 663a7924-1e60-448a-81b3-c1063f1c4060:

Fileload_reasonmemory_typedistinct loads in single compact event
.claude/rules/coaching-style.mdcompactProject3
.claude/rules/memory-management.mdcompactProject3
~/.claude/CLAUDE.mdcompactUser3
.claude/rules/anti-sycophancy-contract.mdcompactProject3
(every other file in .claude/rules/)compactProject3

Same pattern at session_start for some sessions (2-3× per file), single-emission for others.

Reproducer (any project with multiple .claude/rules/*.md files):

# .claude/hooks/instructions-loaded-logger.py
import json, sys, time
payload = json.loads(sys.stdin.read())
with open("/tmp/il_log.jsonl", "a") as f:
    f.write(json.dumps({
        "ts": time.time(),
        "file_path": payload.get("file_path"),
        "load_reason": payload.get("load_reason"),
        "memory_type": payload.get("memory_type"),
    }) + "\n")

Wire under InstructionsLoaded in .claude/settings.json. Run a session that triggers a compact. Inspect /tmp/il_log.jsonl — each file path appears 3× per compact event.

Why it matters

For repos with 20-50 .claude/rules/*.md files (~150 KB total), every compact event emits ~450 KB of InstructionsLoaded callbacks corresponding to ~450 KB of context reload. Combined with the 5-minute prompt-cache TTL default (#46829), this creates a positive-feedback loop: compaction triggers near-saturation context reload, which accelerates the next compaction.

Measured impact in one project (~50 active rules): ~87 KB → ~261 KB instruction reload per compact (3× multiplier confirmed). Roughly half of total session-level context overhead.

Expected behavior

Each instruction file should fire InstructionsLoaded exactly once per load event, matching the user-experience semantics ("compact happened, context was reloaded").

Suggested fix paths

  1. De-duplicate at emission: only fire InstructionsLoaded once per (session_id, file_path, load_reason, parent_file_path) tuple per logical event.

  2. Surface the multi-pass intent: if 3× is intentional (scan/validate/load phases), expose a phase field in the hook payload so callers can de-duplicate or log meaningfully.

  3. Document current behavior under InstructionsLoaded in https://docs.claude.com/en/docs/claude-code/hooks so users instrumenting it know to expect multi-emission.

Related

  • #46829 (cache TTL regression) — same architectural area; combined effect compounds context burn
  • #38524 (ContextThreshold hook proposal) — would let users react to saturation but doesn't address upstream waste
  • #49226 (context_utilization API request) — observability layer that would make this trivially visible

Environment

  • Claude Code: latest stable (Apr 2026)
  • Platform: macOS (Darwin 25.4.0)
  • ~5,300 instruction file load events captured in DuckDB before reporting

extent analysis

TL;DR

The InstructionsLoaded hook firing three times per instruction file per compact event can be mitigated by de-duplicating emissions based on a unique tuple or exposing a phase field in the hook payload.

Guidance

  • Verify the issue by running the provided reproducer and inspecting the /tmp/il_log.jsonl file to confirm the triple emission pattern.
  • Consider implementing a de-duplication mechanism at the emission level, using a unique tuple such as (session_id, file_path, load_reason, parent_file_path) to ensure InstructionsLoaded fires only once per logical event.
  • If the triple emission is intentional, exposing a phase field in the hook payload could help callers de-duplicate or log meaningfully, reducing the impact of the issue.
  • Review the related issues (#46829, #38524, #49226) to understand the broader architectural context and potential compounding effects.

Example

No code snippet is provided as the issue is more related to the behavior of the InstructionsLoaded hook rather than a specific code implementation.

Notes

The provided reproducer and telemetry data suggest a consistent pattern of triple emission, but the root cause and intended behavior are unclear. The suggested fix paths (de-duplication or exposing a phase field) may require further investigation and validation.

Recommendation

Apply a workaround by de-duplicating InstructionsLoaded emissions based on a unique tuple, as this approach can help mitigate the issue without requiring changes to the underlying hook behavior.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Each instruction file should fire InstructionsLoaded exactly once per load event, matching the user-experience semantics ("compact happened, context was reloaded").

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING