You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Compaction review fixes + payload-capture diagnostic
Three-persona review of the shipped compaction re-injection feature. Headline:
the feature is unverified speculative plumbing — its primary channel
(SessionStart source=compact additionalContext) is documented as dropped on
current Claude Code (#15174), and the PostCompact-stdout fallback is an
unconfirmed assumption. The good news the security review confirmed: the task
#51 prompt-injection fence is fully preserved on both emit paths.
Applied the cheap safety + correctness batch (not the larger hooklib refactor):
- hook_compact now asserts its own identity: main(default_event="PostCompact").
If the host omits hook_event_name, a real PostCompact no longer falls through
to the SessionStart JSON format on the very path this hook exists to serve.
- Emit path is now non-fatal: a BrokenPipeError while writing the block is
swallowed (return 0) instead of escaping to SystemExit as a traceback +
nonzero exit, honoring the never-break-the-session contract.
- Compaction no longer does heavy work in the hook's critical path: build_block
takes fresh=, and on a compaction (fresh=False) we query the already-built
shared index instead of running a full project reindex + 500-row pool
re-mirror + embed backfill on every /compact. Fixed the misleading
"Cheap: a local store read" comment.
- Double-injection guard + breadcrumb: we register BOTH SessionStart(compact)
and PostCompact (a host hedge); on a host that honors both, the block would be
injected twice. A state.json breadcrumb (last_compact_reinject {key,event,ts})
written on emit lets the sibling event no-op within a 45s window — and doubles
as on-device observability for which path actually fired. Uses the existing
atomic+locked update_state.
- Installer matches komi hooks by COMMAND SHAPE (regex on `-m
komi.adapters.claude_code.<module>`), not a loose substring. The old
substring test could self-heal-overwrite or uninstall-remove an unrelated user
hook that merely mentioned the module path; PostCompact widened that surface.
- Bounded the hook stdin read at 4MB.
New: payload-capture diagnostic to finally verify on-device.
- hook_capture.py records the raw stdin Claude Code sends (entry event,
parsed keys, hook_event_name/source/trigger/session_id) to
~/.claude/komi/_hook_capture.jsonl, then delegates to normal recall so the
session is unaffected.
- `komi-learn capture on|off|show`: `on` re-points SessionStart+PostCompact at
the recorder, `show` prints what fired, `off` restores normal hooks.
This is how we'll answer "does re-injection actually work on this host."
Tests: test_compaction_fixes.py (16) — explicit-event routing, emit broken-pipe
swallowed, fresh=False on compaction / True on session start, _merged_store skip,
dedup guard + per-session breadcrumb + end-to-end second-sibling no-op, bounded
stdin, command-shape matching (lookalike user hook survives install+uninstall),
capture on/off. Updated test_compact_reinjection.py mocks for the new build_block
signature. Full suite 282 passed, 1 skip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* update: verify the AGENT's behavior was upgraded, not just the CLI
A user asked exactly the right question: `update` upgrades the CLI, but does the
coding agent's behavior (recall/distill/compaction) actually change too?
Answer: yes — the agent behavior IS the komi package. The hooks invoke
`python -m komi.adapters.claude_code.hook_recall` with a pinned interpreter, so
upgrading that interpreter's site-packages means the next hook firing runs the new
code. CLI and agent are one shipment, not two. The ONE caveat: the upgrade has to
land in the same Python the hooks are pinned to. `pip install -U` upgrades the env
it runs in; if the CLI lives in a different venv than the hooks, the CLI updates
and the agent silently stays old.
So make "the behavior is updated" a verified fact, not an assumption:
- setup.hook_interpreters() reads the actual Python each installed komi hook runs
under (parsing the interpreter token out of the command line, quoted or not).
- updater.installed_version_via_subprocess(python=) can now query ANY interpreter's
komi-learn version, not just the CLI's.
- After a successful upgrade, cmd_update asks each hook interpreter what version it
imports:
* match -> "agent behavior updated — hooks run X (verified)"
* differ -> loudly warns the agent is still on old code + prints the exact
`"<that python>" -m pip install --upgrade komi-learn` to fix it
* no hooks -> tells the user to run `komi-learn install` to enable the agent
Proven on the real machine: reports the hook interpreter is current with proof.
README: `update` now reads "upgrade komi-learn + the agent's hooks".
Tests: 6 added in test_compaction_fixes.py — interpreter extraction (quoted/plain),
hook_interpreters with/without install, verify match / stale-other-python (the
critical divergence case, asserts the fix line) / no-hooks. Made the cmd_update CLI
tests hermetic by neutering _verify_agent_updated (it shells the real hook Python;
it has its own dedicated tests). Full suite 288 passed, 1 skip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Address ultrareview findings (7 nits, all real consistency gaps)
/ultrareview on this PR returned 7 findings, all severity "nit" but each tracing
to an invariant the PR itself introduced. Fixed all:
- _emit (hook_recall) is now event-aware + pipe-safe. The three early-exit no-op
paths (dedup, recall-failure, empty-block) previously emitted JSON even on
PostCompact — where stdout is appended verbatim to the model context, so a
diagnostic {"_note": ...} blob would pollute it — and two of them sat OUTSIDE
the broken-pipe try/except. _emit now suppresses output on PostCompact and
swallows write errors, so every caller inherits both protections. (merged_bug_005)
- hook_capture bounds its stdin read with the same _MAX_STDIN_BYTES cap as
hook_recall. set_capture re-points the real events at hook_capture, so the cap
must not be lost on those routes. (bug_001)
- install no longer silently disables active capture: if an existing hook is a
hook_capture command, _install_hooks leaves it and reports "capture left ON
(run komi-learn capture off to restore)" instead of overwriting it as a benign
"refreshed". (bug_013)
- set_capture no longer re-installs removed hooks: it refuses up front when komi
isn't installed (matching the missing-settings.json guard) and skips — never
appends — per-event. So `capture on/off` after `uninstall` can't silently
resurrect the hooks. (bug_016)
- _verify_agent_updated splits the stale message on same_as_cli. A stale result
on the SAME interpreter the CLI just upgraded (the couldn't-confirm fallback)
now says "the upgrade did not land in this interpreter — retry/elevate/check
pin" instead of the self-contradictory "DIFFERENT Python than the one just
upgraded" with a fix command identical to the one that ran. (bug_015)
- paths.read_state(): a lock-briefly-read-no-write helper. _compaction_already_served
was calling update_state(identity) — its docstring said "read-only" but
update_state always re-serializes + os.replace's state.json, taking an
exclusive flock and rewriting byte-identical content on every compaction event.
Now it only reads. (bug_003)
- Fixed a tautological test: test_hook_interpreters_extracts_pinned_python had
`assert X or interps[0]` (always true); now asserts the extracted interpreter
equals sys.executable, so the invariant _verify_agent_updated relies on is
actually guarded. (bug_008)
Tests: +11 (emit suppression/json/broken-pipe, dedup-postcompact-empty, capture
stdin bound, install-keeps-capture, set_capture-refuses-uninstalled,
verify-same-python-message, read_state-no-write) and corrected the empty-block /
recall-failure tests to assert the new per-event behavior. Full suite 299 passed,
1 skip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
0 commit comments