Skip to content

Add Claude Code hook capture for tool and lifecycle events - #1067

Merged
lightzt99 merged 6 commits into
oceanbase:mainfrom
wayyoungboy:codex/issue-1041-claude-hook-events
Jun 30, 2026
Merged

Add Claude Code hook capture for tool and lifecycle events#1067
lightzt99 merged 6 commits into
oceanbase:mainfrom
wayyoungboy:codex/issue-1041-claude-hook-events

Conversation

@wayyoungboy

@wayyoungboy wayyoungboy commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

  • capture Claude Code SessionStart, PostToolUse, PostToolUseFailure, PreCompact, Stop, subagent, and task lifecycle events with bounded summaries and no-LLM regression coverage
  • keep hook privacy behavior aligned with the shared scrubber, including SessionStart search scrubbing, POWERMEM_HOOK_SECRET_ACTION=block handling for detached worker handoff, and parent-side scrub/block before lifecycle worker payload files are written
  • record official task lifecycle schema fields such as task_subject, task_description, teammate_name, and team_name
  • keep generated hooks/bin/powermem-hook-* binaries committed so Git/marketplace installs and release/plugin zips include runnable native hooks
  • document source-checkout install behavior, event-driven hook writes/search, and the rebuild-only path for refreshing hook binaries from source changes
  • remove full lifecycle raw_payload metadata and keep bounded, allowlisted lifecycle link fields plus numeric token usage

Fixes #1040
Fixes #1041
Fixes #1042
Fixes #1043
Fixes #1044
Fixes #1045

Testing

  • go test ./... from apps/claude-code-plugin
  • go test -count=1 ./... from apps/claude-code-plugin
  • after activating the project virtual environment, python -m py_compile tests/regression/test_claude_hook_no_llm.py
  • after activating the project virtual environment, POWERMEM_HOOK_BIN=apps/claude-code-plugin/hooks/bin/powermem-hook-linux-amd64 python tests/regression/test_claude_hook_no_llm.py
  • bash apps/claude-code-plugin/scripts/build-hook-binaries.sh
  • bash apps/claude-code-plugin/scripts/package-plugin.sh
  • unzip -l apps/claude-code-plugin/dist/powermem-claude-code-plugin-0.1.0.zip | rg 'hooks/bin/powermem-hook'
  • git archive --format=tar HEAD apps/claude-code-plugin | tar -tf - | rg 'apps/claude-code-plugin/hooks/bin/powermem-hook'
  • git diff --check
  • two independent final reviews completed with no blocking findings
  • GitHub Actions checks passed for the pushed head commit

@wayyoungboy

Copy link
Copy Markdown
Member Author

@Necmttn This draft implements the suggestions from #1041, #1044, and #1045. Could you review it when you have time?

@wayyoungboy

Copy link
Copy Markdown
Member Author

Added focused Go unit tests for the Claude Code hook helpers.

The new tests cover default-on scrubbing, tool include/exclude precedence, bounded PostToolUse handoff without raw tool payloads, Agent response linking/content blocks, unknown-tool shape summaries, PreCompact tail bounds/offsets, and lifecycle kind derivation from the hook event name.

Validation:

  • go test ./... from apps/claude-code-plugin
  • python -m py_compile tests/regression/test_claude_hook_no_llm.py
  • python tests/regression/test_claude_hook_no_llm.py
  • make test-claude-hook-docker

@wayyoungboy
wayyoungboy marked this pull request as ready for review June 21, 2026 15:38

@wayyoungboy wayyoungboy left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding coverage for the newer Claude Code hook events. I found one blocking install-path regression and two data-flow/documentation issues. I cannot submit a formal request-changes review from this account on this PR, so leaving this as a review comment instead.

Findings:

  • Blocking: marketplace/Git installs can lose hooks/bin/powermem-hook-* because the root marketplace source still points at ./apps/claude-code-plugin, while this PR removes the tracked binaries and ignores hooks/bin/; hooks/run-hook.sh then exits successfully when the binary is missing.
  • Lifecycle metadata stores the entire scrubbed raw_payload by default for subagent/task events. Real lifecycle payloads can include final assistant text, task details, and session/background task fields, and this path is not bounded or allowlisted like the tool/precompact summaries.
  • The troubleshooting section still says writes come from SessionEnd / PostCompact, but the default hook table now includes write traffic from PreCompact, tool success/failure, and lifecycle hooks during the session. That under-discloses the new default data-flow/privacy surface.

Validation I ran:

  • go test ./... in apps/claude-code-plugin
  • python -m py_compile tests/regression/test_claude_hook_no_llm.py after activating the project virtual environment
  • POWERMEM_HOOK_BIN=apps/claude-code-plugin/hooks/bin/powermem-hook-linux-amd64 python tests/regression/test_claude_hook_no_llm.py after activating the project virtual environment
  • bash apps/claude-code-plugin/scripts/build-hook-binaries.sh
  • go test -count=1 ./... in apps/claude-code-plugin
  • bash apps/claude-code-plugin/scripts/package-plugin.sh and checked the zip contains the hook binaries
  • git diff --check

Comment thread apps/claude-code-plugin/.gitignore Outdated
@@ -1,2 +1,3 @@
dist/
*.zip
hooks/bin/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: after ignoring and deleting hooks/bin, the marketplace install path still pulls the plugin from the Git source tree (.claude-plugin/marketplace.json uses ./apps/claude-code-plugin). A Git or marketplace install will not include generated binaries, and hooks/run-hook.sh exits successfully when hooks/bin/powermem-hook-* is absent. The release zip path works because the packaging script builds binaries, but marketplace installs get inert hooks unless the binaries are shipped another way or the hook builds/locates them at install/runtime.

"schema_version": 1,
"scrub_mode": hookScrubEnabled(),
"infer_mode": infer,
"raw_payload": rawPayload,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stores the entire scrubbed lifecycle payload in memory metadata by default. Real SubagentStop payloads include final assistant content and background task/session fields, and task payloads can contain detailed task descriptions. Unlike the bounded tool and precompact summaries, raw_payload is not bounded or allowlisted, so one lifecycle event can persist arbitrarily large or sensitive text. Please store only selected link fields and bounded summaries, or make raw payload capture opt-in.

@wayyoungboy

Copy link
Copy Markdown
Member Author

Re-review status for the current head: the prior review findings appear addressed in the latest update.

  • Git/marketplace install path: hook binaries are committed under hooks/bin/, and the docs/package flow now say Git/marketplace installs use the committed binaries.
  • Lifecycle privacy: lifecycle metadata no longer stores the full scrubbed raw_payload by default, and tests assert that it is absent.
  • Documentation/data-flow disclosure: docs now describe the expanded write hooks, including PreCompact, tool success/failure events, and lifecycle events.

Current checks are green. I do not see a new blocking issue from this static re-review. I am not marking this LGTM because this PR is authored from the wayyoungboy branch and still needs independent maintainer review/acceptance.

@Necmttn

Necmttn commented Jun 30, 2026

Copy link
Copy Markdown

Did a focused re-review of current head 4c4987a against the constraints from #1041:

  • Hook paths stay fail-open at dispatch time, and the write paths move work into detached workers.
  • PostToolUse now builds bounded structured summaries before worker handoff instead of carrying raw tool_input / tool_response through.
  • Success events include a deterministic event_id when session_id and tool_use_id are available, and tool-event infer is off by default.
  • Include/exclude precedence has exclude winning, including wildcard cases.
  • Scrubbing is default-on and is applied before worker payload files; lifecycle metadata is allowlisted/bounded rather than copying raw_payload.
  • The regression fixtures cover Bash, Agent, Write, wildcard unknown tools, include/exclude, capture disable, failure capture, interrupts, PreCompact, Stop, SubagentStop, and TaskCompleted.

Local validation I could run here:

  • python3 -m py_compile tests/regression/test_claude_hook_no_llm.py passes.

Local validation I could not complete here:

  • go test ./... from apps/claude-code-plugin could not run because this machine has no go binary.
  • python3 tests/regression/test_claude_hook_no_llm.py also stops at setup for the same reason, because it builds ./cmd/powermem-hook with go build.

I do not see a new blocker from the static pass, but I would still rely on CI or a maintainer environment with Go installed for the final Go/regression signal before merge.

Generated with ax.

@lightzt99

lightzt99 commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Verified successfully in a Go environment.

LGTM.

@lightzt99
lightzt99 merged commit 462b287 into oceanbase:main Jun 30, 2026
27 checks passed
222twotwotwo pushed a commit to 222twotwotwo/powercontext that referenced this pull request Aug 23, 2026
…#1067)

* Add Claude Code hook event capture

* Add unit tests for Claude hook helpers

* Add Claude hook session and failure events

* Fix Claude hook lifecycle handoff privacy

* fix: address claude hook review findings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment