PostCompact hook silent-miss and forced compaction detection failure on Windows 11 / git BASH

Status Open
Reported on v2.1.210
Maintainer reply None cached
Activity 7 comments · opened Jul 15, 2026

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.json hooks 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

  1. PostCompact hook fires reliably on every compaction event, producing its additionalContext payload in the model's resume context.
  2. 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.
  3. 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.json hooks block
  • The post-compact-reload.sh script content and its expected output
  • Screenshot / recording of the terminal-display disturbance if that would help diagnose the companion issue

View original on GitHub ↗

4 Comments

jmiramonti · 1 month ago

Two more silent-miss data points to add since the original filing 2026-07-15:

Data point #5 (2026-07-17 morning): --resume with 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 the HOOK_FIRED_OK marker + any hook-side context re-injection is lost. This suggests the underlying claudeMd frontmatter 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.sh today 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.

jmiramonti · 1 month ago

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 --resume to restore prior session state.

What happened:

  1. 2026-07-20T13:27:12Z — SessionStart hook fired successfully. Confirmed via a last_turn_context.txt log file that my SessionStart / PostCompact scripts append to on every fire (added 2026-07-17 as diagnostic instrumentation).
  2. ~2026-07-20T13:55:00Z — user sent first message (load report), ~28 minutes after hook fire.
  3. At user's first message, the model's turn-open context did NOT include the HOOK_FIRED_OK marker or any of the additionalContext the hook script emits. Manual backstop replay was still needed.
  4. User confirmed post-hoc: claude --resume was the command that opened the session between hook fire and first message.

Interpretation:

The --resume command 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 --resume itself 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.txt append 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:

FIRE_TIMESTAMP="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
FIRE_SOURCE="${HOOK_FIRE_SOURCE:-auto-session-start}"
echo "${FIRE_TIMESTAMP} ${FIRE_SOURCE}" >> "$PROJECT_ROOT/.claude/last_turn_context.txt"

Also refined my local source-tag differentiation to distinguish 3 fire sources:

  • auto-session-start — SessionStart hook fires session-start-load-v2 directly
  • auto-post-compact — PostCompact hook fires post-compact-reload which delegates to v2
  • manual-backstop-replay — Claude invokes the reload script from Bash for recovery (with HOOK_FIRE_SOURCE env var prefix)

Suggested investigation angle: if --resume is 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---resume additionalContext, would fix the observable gap.

Running count: 6 documented events over 3 weeks (2026-06-29 → 2026-07-20). Pattern breakdown:

  • 5 events with hook NEVER fired (data points #1-#5, #6)
  • 1 event with hook FIRED but additionalContext stripped by --resume (data point #7, this event)

Recovery continues to work reliably via the manual backstop pattern. Not blocking on this end.

jmiramonti · 1 month ago

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.txt most-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 --resume after cold-start) + 1 event with hook FIRED but additionalContext stripped by --resume intercept (data point #7). Not blocking on this end — the manual backstop pattern continues to work reliably. Just capturing the incremental data.

jmiramonti · 1 month ago

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 --resume command.

What the file-based fire log shows:

2026-07-27T14:40:02Z manual-backstop-replay-monday-return
2026-07-27T18:27:48Z auto-session-start                     ← auto-fire post-reboot
2026-07-27T18:42:51Z manual-backstop-replay-post-windows-hangup

The auto-session-start at 18:27:48Z confirms the SessionStart hook fired automatically after the Windows-reboot-triggered session restart. The last_turn_context.txt instrumentation captured this cleanly.

But the model didn't see it. No HOOK_FIRED_OK marker 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:

  • A <system-reminder> announcing new agent types available ("Explore", "Plan") that weren't in the prior agent list
  • A <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 session
  • No continuation-marker phrase
  • No date-change reminder (same-day event)

The "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 --resume and 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:

  • 6 events with hook NEVER fired on the expected compaction (data points #1, #2, #3, #5, #6, #8)
  • 2 events with hook FIRED but additionalContext stripped/not propagated (#7 via --resume; #9 via mid-session Windows reboot)
  • 1 event where partial-context resumption looked normal externally but internal state was lost silently (data point #4 — noted for completeness)

Recovery via manual backstop continues to work reliably. Not blocking. Just capturing incremental data — happy to keep updating as events accumulate.

Showing cached comments. Read the full discussion on GitHub ↗