Skip to content

Deliver the compaction briefing through the hook that can inject it - #522

Merged
zzet merged 2 commits into
mainfrom
fix/deliver-the-compaction-briefing-through-sessionstart
Aug 9, 2026
Merged

Deliver the compaction briefing through the hook that can inject it#522
zzet merged 2 commits into
mainfrom
fix/deliver-the-compaction-briefing-through-sessionstart

Conversation

@zzet

@zzet zzet commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Investigating #70 turned up that the reporter was right about the mechanism, and that a gortex-side bug sitting underneath it was worse than the issue itself.

The PreCompact briefing never reached the model

runPreCompact emitted its orientation snapshot as hookSpecificOutput.additionalContext. PreCompact has no context-injection contract in Claude Code. Per the hooks reference, additionalContext is honoured for SessionStart, Setup, SubagentStart, the tool events, Stop and SubagentStop; PreCompact's only documented output is a top-level decision: "block" (or exit 2), both of which block compaction. Its plain stdout is not context either — only UserPromptSubmit, UserPromptExpansion and SessionStart get that.

So the entire briefing — graph stats, symbol history, hotspots, feedback, and the "do not re-read indexed files" instruction — was discarded. As far as I can tell it never worked.

It now ships from SessionStart(source="compact"), which does honour additionalContext and fires once the new window is built. The Source field was already parsed and already carried "compact"; it was simply never read. Gortex registers SessionStart with no matcher, so no installer change is needed.

The block now says why it matters

This is the substance of #70. Compaction rebuilds the window from the summary, the most recent exchanges, and up to five recently-read files, which Claude Code re-reads from disk and re-injects as system-reminder blocks. That path invokes the Read tool directly, so no PreToolUse hook sees it and none of gortex's redirects to graph tools apply — the agent wakes up holding raw whole-file content it never asked for, and will happily read those files again.

The briefing degrades to advisory-only when the bridge is unreachable, rather than to silence as before: the advisory costs no daemon call and is the half that actually changes what the agent does next.

PreCompact stays registered so hook-effectiveness telemetry still records compactions, and now emits nothing.

Second fix: the doubled version prefix

The daemon status line rendered vv0.63.2 on every session start — the same shape reported in #70 as vv0.41.0+29a0849. StatusResponse.Version arrives already v-prefixed and renderDaemonReadiness added its own literal v at all three of its daemon-state branches.

renderLeanReadiness directly above it already trimmed the prefix, which made this look half-fixed. It is not — no shipped instruction profile selects the lean tier, so that renderer never runs and every real session took the broken path. Both now route through one helper, tolerant of an unprefixed value because canonicalVersion's fallback branch returns one.

Docs

  • docs/agents.md listed 4 installed hooks; the installer writes 8. Also documents why the post-compaction block hangs off SessionStart.
  • docs/features.md claimed PreCompact injects the snapshot.

docs/agents.md:318's "PreCompact / PostCompact — Not installed" row is not wrong and is untouched: it sits under the Codex "Intentional Codex lifecycle gaps" table.

Verification

Unit tests plus an end-to-end run of the built binary against a live daemon:

payload result
SessionStart source=compact block + advisory + rule preamble, no vv
SessionStart source=startup no block; renders (v0.63.2-39-g78a79eaf+78a79eaf, …), previously (vv0.63.2-…
PreCompact 0 bytes stdout, 0 bytes stderr

go test -race ./internal/hooks/ passes and golangci-lint reports 0 issues. The first commit was checked out standalone to confirm it compiles, since the two fixes were split out of one file.

Not addressed here

Three defects found during the investigation, deliberately left out of this PR:

  • HookTierLean is unreachable — no shipped profile selects it, so renderLeanReadiness is dead code. Wiring bug or reserved tier is a product call.
  • trackedRepoForPath (session_paths.go:210) compares paths byte-exactly, which fails on any case-insensitive filesystem — macOS as much as Windows — while a case-folding helper exists and is used elsewhere. Live enforcement hole.
  • The baked hook command (claudecode/hooks.go:131) is unquoted, so an install path containing a space yields a command the shell splits and no hook fires. Latent, every OS.

Refs #70.

zzet added 2 commits August 9, 2026 16:56
The PreCompact hook emitted its orientation snapshot as
hookSpecificOutput.additionalContext. PreCompact has no context-injection
contract in Claude Code: additionalContext is honoured for SessionStart,
Setup, SubagentStart, the tool events, Stop and SubagentStop, and
PreCompact's only documented output is a top-level `decision: "block"`,
which blocks compaction. Its plain stdout is not context either — only
UserPromptSubmit, UserPromptExpansion and SessionStart get that. So the
whole briefing — graph stats, symbol history, hotspots, feedback, and
the "do not re-read indexed files" instruction — was discarded.

Move it to SessionStart(source="compact"), which does honour
additionalContext and fires once the new window is built. The Source
field was already parsed and already carried "compact"; it was just
never read.

The block now also names the reason it matters. Claude Code rebuilds a
compacted window from the summary, the most recent exchanges, and up to
five recently-read files that it re-reads from disk and re-injects as
system-reminder blocks. That path invokes the Read tool directly, so no
PreToolUse hook sees it and none of the redirects to graph tools apply:
the agent wakes up holding raw whole-file content it never asked for.
Saying so is what stops it paying for those bytes a second time.

The briefing degrades to advisory-only when the bridge is unreachable
rather than to silence — the advisory needs no daemon call and is the
half that actually changes what the agent does next.

PreCompact stays registered so hook-effectiveness telemetry still
records compactions, and now emits nothing at all.
The daemon status line rendered "vv0.63.2" — every session start, on
the default hook tier. StatusResponse.Version arrives already
v-prefixed, and renderDaemonReadiness added its own literal "v" at all
three of its daemon-state branches.

renderLeanReadiness, immediately above it, already trimmed the prefix,
which made the bug look half-fixed. It is not: no shipped instruction
profile selects the lean tier, so that renderer never runs and every
real session took the broken path.

Route both through one helper. The trim has to tolerate an unprefixed
value too — canonicalVersion composes the "v" through Version.String()
on its happy path, but its fallback branch returns the bare version.
@zzet
zzet merged commit c655de1 into main Aug 9, 2026
11 checks passed
@zzet
zzet deleted the fix/deliver-the-compaction-briefing-through-sessionstart branch August 9, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant