Skip to content

Add PreCompact hook to preserve original goal and hard constraints #27

Description

@severity1

Part of #26.

Problem

As conversations get longer, Claude Code compacts older messages to stay within the context window. The compaction summary frequently drops:

  • The user's original ask (the why)
  • Hard constraints declared early ("don't touch the database layer", "preserve behavior")
  • CLAUDE.md rules that were loaded once and never re-loaded

Result: Claude drifts from the original intent. anthropics/claude-code#22638 documents this exact failure mode — Claude ignored CLAUDE.md rules mid-session and executed a destructive git command. Community writeups (Fortune, The Register, dev.to "Feb-Mar 2026 regression") cite instruction drop-off as one of the top complaints.

Proposed solution

Add a PreCompact hook (released v2.1.105, Apr 13 2026) that:

  1. Reads the original user prompt(s) and any explicit constraints from the conversation
  2. Re-injects them as additionalContext immediately after compaction so they survive into the post-compaction window
  3. Optionally returns {"decision":"block"} if the constraints would be lost and the user has opted into strict mode

File changes

  • New: scripts/preserve-context.py — PreCompact hook handler
  • Update: hooks/hooks.json — register the new event
  • Update: README.md — document the new behavior + opt-out
  • New tests: tests/test_precompact_hook.py

Implementation sketch

# scripts/preserve-context.py
import json, sys

data = json.load(sys.stdin)
# Walk transcript_path; find first user message + any "constraint markers"
# (lines containing "must", "do not", "preserve", "only", "never")
preserved = extract_constraints(data["transcript_path"])  # truncated to budget

print(json.dumps({
    "hookSpecificOutput": {
        "hookEventName": "PreCompact",
        "additionalContext": f"<original-goal-and-constraints>\n{preserved}\n</original-goal-and-constraints>"
    }
}))

Token budget

  • Total injected on compaction: ≤200 tokens, comprising:
    • Original first user prompt: ≤120 tokens (truncate with ellipsis if longer)
    • Detected hard constraints: ≤80 tokens (keep highest-priority first; drop overflow)
  • Zero tokens injected when no compaction is occurring (hook fires only on PreCompact event)
  • Zero tokens injected if no constraints detected and original goal already lives in retained context (skip injection entirely)
  • Constraint extraction runs in <50ms; no LLM calls

Acceptance criteria

  • PreCompact hook fires on compaction events
  • Original first user prompt is re-injected post-compaction (within budget)
  • Detected hard constraints (negation patterns, "must"/"never"/"only") are re-injected
  • Total injection ≤200 tokens (truncate cleanly with … markers)
  • Hook completes in <100ms (no heavy parsing)
  • Bypass: users can disable via env var (PROMPT_IMPROVER_PRESERVE=0)
  • Tests cover: no constraints, multi-constraint, malformed transcript, over-budget truncation

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions