Repository navigation
Conversation
Record what each hook does at its process boundary, so that moving the hosts onto a shared lifecycle module can show that it changed nothing. Each case runs an installed copy of the plugin's hook scripts with a native payload, the host environment, and a scripted Server. Claude Code hooks run without site packages, since python3 provides none of the plugin's dependencies, and Codex hooks run in an environment built from the plugin's lock file, so an import the host cannot satisfy fails the case. Each case records the requests the Server receives, the hook's stdout, stderr, and exit status, every file the hook leaves in the case directory, and the mode of each directory it creates. JSON objects are recorded with sorted keys. The cases cover recall and capture, flushing, the query bound in characters and bytes, credential scrubbing and stored or configured credentials, endpoint precedence and the plaintext transport policy at a prompt and at SessionStart, invalid settings, redirects and non-JSON, oversized, or slow responses, Scope resolution and the ways it fails, failure classification and throttling, the bootstrap receipt that SessionStart hands to the next prompt and clears after a failed start, Codex tool binding, and the Stop statistics. The Claude Code fixture names a closed port as its lower-precedence endpoint, so no case can reach a Server on the default address. A refactoring leaves the fixtures unchanged. A reviewed behavior change rewrites them with `uv run python tests/test_hook_characterization.py`.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
| { | ||
| "hook": "hooks/user_prompt_submit.py", | ||
| "environment": { | ||
| "POWERCONTEXT_CLAUDE_REQUEST_TIMEOUT_SECONDS": "0.3" |
There was a problem hiding this comment.
[P2] Give the slow-response fixtures more scheduling margin
This 0.3-second timeout also applies to the prerequisite Scope resolution and the subsequent capture request. On the unchanged head, a serial Linux/Python 3.14 run under high system load produced 105 passes and two failures: both hosts' slow-preparation-times-out cases timed out during Scope resolution, so the expected prepare/capture requests never happened. The Claude Code case also failed in isolation. In a control run, increasing the request timeout to 1 second while retaining the scripted 2-second preparation delay matched the original snapshots for both hosts.
Please increase the scheduling margin for these requests and adjust the delayed response as needed, so the intended preparation timeout remains distinguishable from a slow test runner. The same issue applies to codex.json:1965; otherwise normal parallel or loaded test runs can report a behavior regression without any hook changes.
There was a problem hiding this comment.
Thanks, fixed in a060e97. Both slow-preparation-times-out cases now set a 1 s request timeout, and the scripted preparation answers after 5 s instead of 2 s, so the prepare request still times out first, with a 4 s margin. Re-recording leaves the expectations unchanged, and each case now takes about 1.3 s.
I couldn't reproduce the failure with 36 busy loops on an 18-core Mac, so I checked the mechanism directly: with the scripted Scope response delayed by 0.4 s, both cases fail with the old 0.3 s timeout. With the new values, both pass with the Scope response delayed by 0.4 s or 0.8 s.
…argin The two slow-preparation cases set a 0.3 s request timeout, and that timeout also bounds the step's Scope resolution and capture requests. On a loaded runner, Scope resolution could miss it, so the case failed without any hook change. The cases now use a 1 s timeout, and the scripted preparation answers after 5 s instead of 2 s, so the prepare request still times out first with a 4 s margin. The recorded expectations are unchanged.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
hireview001
left a comment
There was a problem hiding this comment.
- CI failed: Upgrade release gate (failure).
- CI failed: tests (3.14, unit) (failure).
- CI failed: tests (3.11, unit) (failure).
- CI failed: tests (3.12, e2e) (failure).
- CI failed: tests (3.12, unit) (failure).
- CI failed: tests (3.13, unit) (failure).
- CI failed: tests (3.14, e2e) (failure).
- CI failed: tests (3.13, e2e) (failure).
- CI failed: tests (3.11, e2e) (failure).
- CI failed: upgrade-acceptance / Prepare upgrade artifacts and matrix (failure).
- Additional CI issues: PR checks.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
hidb4ai
left a comment
There was a problem hiding this comment.
The previous timeout finding is fixed for both hosts; replaying the delayed-Scope counterexample preserves the expected behavior. No remaining findings. This PR changes only tests and fixtures, with no public API or database schema changes.
Which issue or RFC does this PR close?
Part of #2250 (plan step 1, second item: characterization fixtures for Claude Code and Codex).
Rationale for this change
#2250 plans to move the Claude Code and Codex hooks onto one lifecycle module. Their unit tests replace hook internals in about 370 places, most often
resolve_scope_id,_post_json,_prepare_context, and_capture_prompt. Those tests follow the current structure, so a move would rewrite them, and they could not show that it kept the behavior.This PR records what each hook does at its process boundary, which a structural change has to leave as it is. It also runs each hook with the interpreter its host uses. The unit tests run the hooks in the repository's virtual environment, so they cannot notice an import that the host cannot satisfy.
What changes are included in this PR?
tests/test_hook_characterization.pyruns each case against an installed copy of the plugin's hook scripts:python3, which has none of the plugin's dependencies, so they run underpython -S. Codex starts them withuv run --frozen, so they run in an environment thatuv sync --frozenbuilds once per test session from the plugin'spyproject.tomlanduv.lock.$CWD,$SERVER_URL, and$WORKSPACE_KEY, and a time written during the case as$NOW.no_proxy=*.tests/fixtures/hook_characterization/claude-code.json(57 cases) andcodex.json(50 cases). They cover:uv run python tests/test_hook_characterization.pyrewrites the expectations, and the fixture diff shows what changed.The fixtures record today's behavior, including a gap that #2250 lists: when Scope resolution fails, the Claude Code and Codex prompt hooks end without output, although their guides promise a diagnostic. The case descriptions state only what is observable, such as "the prompt is neither prepared nor captured", so the fix for that gap can rewrite those expectations without renaming the cases. The fix is a separate PR.
Are there any user-facing changes?
No. This PR adds a test and its fixtures.
How was this change tested?
tests/test_hook_characterization.py: 107 passed on macOS with Python 3.13, in 7 s with-n auto, the worker settingmake unit-testuses, and in 19 s serially. Python 3.11 and 3.14 also pass serially.Each mutation below changed one place in a plugin. The table lists how many cases failed:
pydanticpowercontextpydantic_settings, which the plugin's lock file provides (control)cwdinstead of the git rootPOWERCONTEXT_CLAUDE_SERVER_URLPOWERCONTEXT_CODEX_AUTHORIZATIONcapture_promptstry(once per host)make checkpassed.make unit-test: 6339 passed, 300 skipped.The cases run the real hook scripts across the real process boundary, but their payloads are built from the hosts' documentation and the existing tests, not recorded from Claude Code or Codex. I did not run either host.
AI usage statement
Claude Code (Claude Opus 5.5) was used to write the test and the fixtures, review them with an independent agent, and draft this description. I reviewed the change.