#!/usr/bin/env bash # Scribe plugin — PreCompact: tell the summarizer what must survive (#3680). # # WHAT THIS HOOK ACTUALLY DOES, and why it is not what the design first assumed. # # Spike #3680 asked whether a PreCompact hook can reach the model, and note # #3679 assumed the channel would be `hookSpecificOutput.additionalContext` # plus a block. Both halves of that are wrong, read out of the installed build # (Claude Code 2.1.273): # # * `additionalContext` is NEVER read on PreCompact. The hook-output schema # has no PreCompact variant at all; the field is honoured for SessionStart, # SubagentStart, Stop and friends, and silently dropped here. # # * A PreCompact hook's STDOUT becomes the compaction's custom instructions. # The handler collects every hook that exited 0 with non-empty stdout and # returns it as `newCustomInstructions`, which is merged with whatever the # operator typed after `/compact` and passed into the prompt that writes # the summary. Every path does this — manual, auto and partial compaction. # # So there IS a model-reaching channel, and it is better than the one designed: # we do not interrupt the compaction, we steer the summary it produces. The # summary is what the next turn reads, so text that survives into it survives # the compaction. # # WHAT NOT TO DO HERE: never block. A PreCompact block (exit 2, or # `{"decision":"block"}`) does not pause for the model and cannot tell it # anything — the compaction is SKIPPED, a warning goes to the operator's # screen, and the session continues uncompacted toward its context limit with # no summary at all. That failure is silent from the model's side, which is # exactly the outcome #3680 existed to avoid shipping. This hook exits 0 on # every path. # # SCOPE: in a subagent the handler discards hook stdout and keeps only a block, # so this steers the main session's compaction and nothing else. That is the # one we care about — a subagent's summary does not outlive it. # # NO NETWORK, NO CONFIG. What must be preserved is already in the conversation # being summarized; this hook's job is to say which parts of it are load-bearing # so the summarizer keeps them literally instead of compressing them away. # Naming the in-flight task ids from the instance would need a server round-trip # inside the compaction path, which is a separate, measurable question. # # PROOF THAT IT FIRED: on a manual `/compact` the handler shows the operator # `PreCompact [] completed successfully: `, so the hook's own # text is the receipt. (Auto-compaction suppresses that notification.) set -uo pipefail # Drain the event so the caller never sees a broken pipe. Nothing in it changes # what we emit: the instruction is the same whether the operator typed # `/compact` or the session hit its limit, and it composes with any custom # instructions they gave, which are merged ahead of ours. cat >/dev/null 2>&1 || true cat <<'EOF' Preserve the following literally in the summary — copied through, not paraphrased or counted: - Every Scribe record the conversation refers to, by id AND title: tasks, issues, milestones, notes, rules, systems. A bare "#4061" is not enough; a record whose name is lost has to be looked up again before it can be used. - Which task is in progress and what its status was last set to, plus the milestone it sits under. - Work that was done but NOT yet recorded in Scribe — an edit with no work-log, a fix not filed as an issue, a decision not written down. Carry these over as outstanding; they exist nowhere else once this conversation is summarized. - Any rule or preference that was retrieved and still governs the work, by id and title. - Anything the operator asked for that has not been done yet, in their words. Everything else here can be recovered from the repository or from Scribe. These cannot: they are this session's only copy. EOF exit 0