PostCompact hook silent-miss and forced compaction detection failure on Windows 11 / git BASH
Summary
The PostCompact hook configured in .claude/settings.local.json does not reliably fire when Claude Code performs a context-compaction event. Across 3 observed compactions over a 2.5-week window (2026-06-29, 2026-06-30, 2026-07-15), the auto-fire rate has been 0/3. The hook script itself is intact and produces the expected additionalContext payload when invoked manually — the failure is in the runtime dispatching the event to the configured hook.
In the most recent incident (2026-07-15), a second-order failure was also observed: no continuation-marker preamble was visible to the model on any user-facing turn following the compaction. This means the model had no textual signal at all that a session boundary had occurred, and it continued responding as if no compaction had happened. The user had to explicitly ask "did you detect the compaction event?" to trigger recovery. We describe a possible causal link to a companion terminal-rendering issue below.
Reporter contact for follow-up: jmiramonti@signalquest.com (GitHub profile does not expose a public email; noting here in case the maintainer wants a direct channel)
Environment
- OS: Microsoft Windows 11 Pro, Build 26200 (10.0.26200)
- Terminal: Windows Terminal, version 1.24.11321.0
- Shell: GNU bash 5.2.37(1)-release (x86_64-pc-msys, MSYS2)
- git: 2.55.0.windows.2
- Claude Code CLI: 2.1.210 (installed via npm at
~/AppData/Roaming/npm/claude) - Model observed (per environment introspection):
claude-opus-4-7 - Hardware: Dell x64 workstation, BIOS 1.29.0 (2026-04-23)
Runtime-update caveat: we cannot determine from our side whether a Claude Code CLI runtime update between 2026-06-30 (data point #2) and 2026-07-15 (data point #3) altered hook-dispatch behavior. If Anthropic can correlate the 3 dates against any PostCompact-relevant runtime change, that would help isolate the root cause.
Reproduction context
We have a .claude/settings.local.json with (schematically):
{
"hooks": {
"SessionStart": [
{ "matcher": "*", "hooks": [{ "type": "command", "command": "bash .claude/scripts/session-start-load-v2.sh" }] }
],
"PostCompact": [
{ "matcher": "*", "hooks": [{ "type": "command", "command": "bash .claude/scripts/post-compact-reload.sh" }] }
]
}
}
Both scripts emit a JSON payload of the form {"additionalContext": "HOOK_FIRED_OK at <ISO-timestamp>\n<load-context-body>"} on stdout. When invoked manually via bash .claude/scripts/post-compact-reload.sh, the payload appears in stdout as expected. On automatic hook-fire we would expect the same additionalContext to appear at the top of the model's auto-loaded context on the resume turn — but it does not.
Observed behavior (3 data points)
Data point #1 — 2026-06-29T19:30Z (PostCompact):
- HOOK_FIRED_OK marker: NOT present in resume context
- Continuation-marker preamble visible: YES ("session is being continued from a previous conversation")
- Consequence: Model continued substantive work for ~6 reply rounds without the load; fabricated a stakeholder-role claim; user caught it via evidence-chain probe
- Recovery: Manual replay of the script at 19:33:44Z produced HOOK_FIRED_OK as expected
Data point #2 — 2026-06-30T21:02Z (PostCompact):
- HOOK_FIRED_OK marker: NOT present in resume context
- Continuation-marker preamble visible: YES
- Consequence: One numeric-carryover slip ("47-row" from skeleton-era assumption); no fabrication (milder round because the auto-summary carried enough detail)
- Recovery: Manual replay at 21:02:39Z
Data point #3 — 2026-07-15T13:19Z (PostCompact) — NEW FAILURE MODE:
- HOOK_FIRED_OK marker: NOT present in resume context
- Continuation-marker preamble visible: NO — no user-facing turn contained the "session is being continued from a previous conversation" phrase
- Only latent signal available to the model: a
<system-reminder>announcing "The date has changed. Today's date is now <new-date>" - Consequence: Model ran ~3 tool-call turns with no self-detection; user had to explicitly ask about recovery
- Recovery: Manual replay at 13:19:28Z
What we've verified locally
.claude/settings.local.jsonhooks block is structurally correct (unchanged across all 3 data points)- Manual invocation of both scripts produces the expected JSON payload with
additionalContext - Hooks were previously observed working on cold session start (SessionStart) — the PostCompact-specific miss is what looks broken
- 3/3 miss rate across dates suggests this is not transient
Companion issue: Windows Terminal display disturbance during compaction — possibly causally related
On this workstation (Windows 11 + Windows Terminal + git BASH), when a forced compaction event occurs, the terminal display is visibly disturbed: the compaction event overwrites part of the terminal log of the operation in progress. The user manually copies terminal output for logging purposes, and after a compaction event the copied log has holes where the compaction ate the prior lines.
Hypothesis (from the project owner): the terminal-rendering disturbance may not be purely a display-layer artifact — it could be the same channel corruption that is causing the model to not see the continuation-marker preamble on data point #3. If some portion of the compaction preamble is being overwritten in the terminal buffer (and by extension, in whatever gets pushed into the model's context on the resume turn), that could explain both the visible terminal corruption AND the invisible-to-model preamble.
If this hypothesis holds, fixing the terminal-rendering issue and the model-preamble-visibility issue may share a root cause. We don't have enough visibility to confirm from our side, but the temporal correlation (the display disturbance happens simultaneously with the compaction event that also affects preamble visibility) is suggestive.
Requested behavior
PostCompacthook fires reliably on every compaction event, producing itsadditionalContextpayload in the model's resume context.- The continuation-marker preamble ("session is being continued from a previous conversation…") is consistently visible to the model on the first post-compaction turn, so that models have a textual signal to trigger recovery discipline even if the hook payload is missing for any reason.
- Terminal-display corruption during compaction on Windows Terminal + git BASH is investigated and fixed — and the possible causal link to the preamble-visibility issue in #2 is evaluated.
What we would find useful
- Confirmation that this is a known issue vs. new
- If unknown: guidance on additional diagnostic data we can collect on our side (e.g., is there a Claude Code log file that records whether the hook was dispatched?)
- Ability to distinguish "hook was dispatched but returned non-JSON" from "hook was never invoked"
Attachments we can supply if useful
- Full 3-entry diagnostic log (
.claude/hook_diagnostic_log.md) — ~250 lines with full incident narratives; can share as gist on request - Sanitized
.claude/settings.local.jsonhooks block - The
post-compact-reload.shscript content and its expected output - Screenshot / recording of the terminal-display disturbance if that would help diagnose the companion issue
Showing cached comments. Read the full discussion on GitHub ↗
4 Comments
Two more silent-miss data points to add since the original filing 2026-07-15:
Data point #5 (2026-07-17 morning):
--resumewith full-context selection following overnight session interruption. SessionStart hook did NOT fire (no HOOK_FIRED_OK marker). Behavior appears to be a distinct scenario from the original PostCompact silent-miss but shares the same underlying "auto-hook doesn't fire" symptom.Data point #6 (2026-07-17 afternoon): Forced compaction MID-SESSION during active workflow. PostCompact hook did NOT fire. Continuation-marker preamble ("This session is being continued from a previous conversation...") was ABSENT — matching the same pattern I noted in the original filing about "stepped-on terminal window content."
Running count: 5 documented silent-miss events over 2.5 weeks (2026-06-29 → 2026-07-17). Miss rate remains 100% across observed events.
Key corroborating observation (data point #6): The end-user independently reported "again it 'stepped' on some of the terminal window content" concurrent with the compaction event, without prompting from me. This is the second independent observation of the terminal-display-disturbance / missing-continuation-marker correlation noted as the Companion Issue in the original filing. Suggests a shared root cause between the display disturbance and the hook-fire failure, and rules out one-off timing coincidence.
Interesting positive finding (from data points #5 and #6): In-session state —
MEMORY.md, task list, tracked-docs context — DOES survive the compaction even when the hook fails. Only theHOOK_FIRED_OKmarker + any hook-side context re-injection is lost. This suggests the underlyingclaudeMdfrontmatter auto-load path is INDEPENDENT of the SessionStart/PostCompact hook — potentially a useful investigation angle if the hook-side code path is proving hard to fix.Local workaround status: the manual backstop replay (
bash .claude/scripts/post-compact-reload.sh) continues to work reliably. Added a lightweight companion.claude/scripts/check-hook-fire.shtoday that only stamps the marker without re-injecting the full ~160KB additionalContext — useful for the common case where in-session state is intact and only the marker is missing.Not blocking on this end; happy to provide additional diagnostic data as it accumulates.
Data point #7 (2026-07-20 morning): new mixed-outcome pattern — hook DID fire, but its additionalContext did NOT reach the model's turn-open context.
Context: Monday morning restart after weekend IT-triggered laptop shutdown. User invoked
claude --resumeto restore prior session state.What happened:
2026-07-20T13:27:12Z— SessionStart hook fired successfully. Confirmed via alast_turn_context.txtlog file that my SessionStart / PostCompact scripts append to on every fire (added 2026-07-17 as diagnostic instrumentation).~2026-07-20T13:55:00Z— user sent first message (load report), ~28 minutes after hook fire.claude --resumewas the command that opened the session between hook fire and first message.Interpretation:
The
--resumecommand appears to strip the fresh session's additionalContext (from the cold-start hook fire) and replace it with the resumed session's prior context. Since--resumeitself doesn't re-fire SessionStart, the resumed session opens with no hook-injected context — even though the cold-start Claude Code launched moments earlier DID fire the hook successfully.This is a new failure mode class distinct from the 5 events documented previously. Prior events had NO auto-fire at all (0/5 rate). This event has auto-fire SUCCESS + downstream propagation FAILURE via
--resume. Rules out "hook never fires" and points to "hook fires but--resume(and possibly other session-state transitions) discard the injected context."Instrumentation that revealed this:
Adding a
last_turn_context.txtappend on every fire made this observable. Previously the only way to detect fire status was via the HOOK_FIRED_OK marker in the model's context — which wouldn't reveal a fired-then-stripped scenario. The file-based fire log is independent of context injection and captures fires that don't reach the model.Bash snippet added to my SessionStart / PostCompact scripts:
Also refined my local source-tag differentiation to distinguish 3 fire sources:
auto-session-start— SessionStart hook fires session-start-load-v2 directlyauto-post-compact— PostCompact hook fires post-compact-reload which delegates to v2manual-backstop-replay— Claude invokes the reload script from Bash for recovery (withHOOK_FIRE_SOURCEenv var prefix)Suggested investigation angle: if
--resumeis expected to preserve session state, does it also preserve (or need to explicitly re-inject) the SessionStart hook's additionalContext? Currently it appears to REPLACE with the prior session's context and lose the fresh cold-start additionalContext. A--resume-triggered re-fire of SessionStart, or explicit preservation of the pre---resumeadditionalContext, would fix the observable gap.Running count: 6 documented events over 3 weeks (2026-06-29 → 2026-07-20). Pattern breakdown:
--resume(data point #7, this event)Recovery continues to work reliably via the manual backstop pattern. Not blocking on this end.
Data point #8 (2026-07-24): forced context-compaction on Windows 11 during active session, resume next morning. PostCompact hook did NOT auto-fire (
last_turn_context.txtmost-recent auto-entry remained the PRIOR compaction from 2026-07-23; no fresh entry corresponding to this compaction). Manual backstop replay at 2026-07-24T13:17:59Z produced HOOK_FIRED_OK as expected.Continuation-marker preamble ("This session is being continued from a previous conversation...") WAS present in the resume context this time — matching data points #1 and #2, differing from data points #3 and #6 where the preamble was absent. So the "no auto-fire on forced compaction" symptom continues to occur across both preamble-present and preamble-absent variants of the scenario.
Running count: 8 documented events over 4 weeks (2026-06-29 → 2026-07-24). Pattern breakdown as of today: 7 events with hook NEVER fired on the expected compaction (data points #1, #2, #3, #5, #6, #8 — mix of preamble-present and preamble-absent, all PostCompact except #5 which was
--resumeafter cold-start) + 1 event with hook FIRED but additionalContext stripped by--resumeintercept (data point #7). Not blocking on this end — the manual backstop pattern continues to work reliably. Just capturing the incremental data.Data point #9 (2026-07-27) — mixed-outcome pattern (auto-fire success + propagation failure), Windows-update-triggered.
Context: mid-session Windows Update forced a system reboot (not user-initiated). CLI process was terminated. Session resumed after reboot — user's next Bash tool input (
pwd) executed cleanly, suggesting the harness picked back up automatically without a--resumecommand.What the file-based fire log shows:
The
auto-session-startat 18:27:48Z confirms the SessionStart hook fired automatically after the Windows-reboot-triggered session restart. Thelast_turn_context.txtinstrumentation captured this cleanly.But the model didn't see it. No
HOOK_FIRED_OKmarker in the model's turn-open context on any user-facing turn between 18:27:48Z and when the user asked "I suspect you didn't detect this and haven't fully recovered, correct?" at ~18:42Z. Same pattern as data point #7 (2026-07-20): auto-fire succeeded, additionalContext did not propagate.New environmental indicator worth documenting: on this session-continuation turn, the model's context now includes:
<system-reminder>announcing new agent types available ("Explore", "Plan") that weren't in the prior agent list<system-reminder>with a fresh skills roster (dataviz, artifact-design, artifact-capabilities, update-config, etc.) — different from what was listed at the start of this sessionThe "new tools/skills appear that weren't there before" pattern is a strong session-boundary signal — worth adding as a trigger for the client-side reflex rule. This scenario type (mid-session Windows reboot from OS updates) is fundamentally different from a user-initiated
--resumeand appears distinct enough to categorize separately from data points #5/#7.Running count: 9 documented events over 4+ weeks (2026-06-29 → 2026-07-27). Pattern breakdown as of today:
--resume; #9 via mid-session Windows reboot)Recovery via manual backstop continues to work reliably. Not blocking. Just capturing incremental data — happy to keep updating as events accumulate.