feat(codex): pin the release hook contract, handler types, and generated wire schemas (#188 S2) - #378
Conversation
…ted wire schemas (#188 S2) Close hooks/hooks.json to the eleven release events, admit every documented command and mcp_tool handler field, reject parsed-but-skipped prompt/agent handlers and per-event rules the host would ignore, byte-pin all 21 rust-v0.147.0 generated hook schemas and validate lifecycle-replay envelopes and codec outputs against them, and publish dated four-state hook-contract capability rows. Interrupt stays deferred with a dated reason.
🦋 Changeset detectedLatest commit: d530a1a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d530a1a7b6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "tool_name": { | ||
| "type": "string" | ||
| }, | ||
| "tool_response": true, |
There was a problem hiding this comment.
Accept non-object PostToolUse responses
When Codex supplies a string, number, boolean, or null tool_response, this newly pinned schema accepts it (true means any JSON value), but validateNativeEventEnvelope in events/projection.ts:289-294 and the generated native-hook wrapper in hook-contract.ts:1157-1162 still require an object. Such a valid PostToolUse invocation therefore fails before reaching the handler; validate presence rather than object shape for Codex, as already done for PermissionRequest's unconstrained tool_input.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed — the pinned rust-v0.147.0 pre-tool-use / post-tool-use input schemas declare "tool_input": true and "tool_response": true, so the object checks in the generated Codex wrapper (hook-contract.ts) and validateNativeEventEnvelope (events/projection.ts) rejected valid scalar/null payloads. Fixed in #404: for Codex both validators now require presence only (mirroring the PermissionRequest tool_input handling); Claude keeps its documented object requirement. Tests cover string/number/boolean/null tool_response and string tool_input through the built Codex PostToolUse wrapper plus the envelope validator on tool/before and tool/after.
There was a problem hiding this comment.
Fixed on main by #404 (c95091d, the #188 follow-up lane): for target === 'codex' both validateNativeEventEnvelope and the generated native hook wrapper require tool_input/tool_response to be present but accept any JSON value, matching the pinned "tool_response": true; Claude keeps the object-shape check. #408 carried the same change and dropped it in favour of #404 during rebase.
…oks (#188 follow-up) (#404) The pinned rust-v0.147.0 pre-tool-use/post-tool-use input schemas declare `"tool_input": true` and `"tool_response": true`, so Codex may deliver scalar or null payloads. The generated Codex hook wrapper and the event-route envelope validator now require presence only for Codex and keep the documented object requirement for Claude. Addresses the PR #378 Codex review thread.
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373)
/#379/#385/#390) (#408) * fix: address late review threads on merged PRs #368/#373/#374/#377/#378/#379/#385/#390 - dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed; select the contract-matrix target from the server's own target list; apply the session timeout per matrix request; observe lifecycle progress through the session trace via a new ContractMatrixClient.observeProgress seam (#385) - playground: wait for a hard-link catalog publisher to release its staging link before adopting the sidecar; return to discovery when the publication is rolled back (#377) - build: run the Agent Plugins byte lane over portable/ during ordinary artifact validation; reject every forbidden control character in header values (#373) - events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378) - api: project only contract fields of adapter capability rows in inspect (#390) - tests/support: digest the real Claude home in the live session guard; isolate USERPROFILE alongside HOME (#374) - docs: Claude plugin-root cwd exception, parked-pin trigger independence, provider typing contract, portable validation moments (#368/#379/#382/#373) * fix(dev): lease before publishing contract status; align fixtures with the portable byte lane - EpochAdoptionPolicy acquires the epoch lease before publishing a passed dev.contract.status and announces adoption synchronously with it, so a status reader never sees "passed" for an epoch that is not yet adopted - hooks.test: Codex PostToolUse accepts a string tool_response and rejects a missing one; Claude keeps the object check - mcp-session-service/public-api-packed fixtures: Agent Plugins forbids placeholders in headers and non-bare/non-./ commands, and ordinary artifact validation now enforces the standard, so the fixtures carry a literal header and a bare `node` command * chore(changeset): drop the Codex tool_response bullet already released by #404 * fix(playground,test): withdraw a failed catalog publication before releasing its staging link; bind custom observeProgress - #persistSnapshot rolls the sidecar back while the staging link still exists when the post-link directory fsync fails, so a concurrent reader keeps seeing an in-progress publication until the path is withdrawn instead of adopting a briefly singly linked file - contractProgressObserver invokes a client's observeProgress method with the client as receiver * chore(changeset): one-paragraph summary ending with the PR reference * fix(playground): recover a catalog staging link abandoned by an exited publisher After the settle deadline, a matching .stage-<pid>-* link whose publisher pid no longer exists is an abandoned publication of an already fsynced sidecar: withdraw the orphan and adopt the sidecar instead of rejecting the epoch forever. A live publisher's staging link is still never yanked. Adds the @internal catalogStagingSettleDeadlineMs seam for deterministic tests. * fix(playground): fsync the catalog directory after withdrawing an abandoned staging link * fix(dev): forward request _meta (progress token) through McpSession and the dev matrix client Lifecycle fixtures pass their generated progressToken as params._meta; the session adapter and McpSession.callTool dropped it, so generated routes never enabled sendProgress and every progress-gated lifecycle fixture failed the dev matrix. McpSessionToolCallOptions and McpClient.callTool now carry _meta. * fix(dev,playground): restart the adoption drain after a handoff race; keep the staging link when a sidecar rollback fails - EpochAdoptionPolicy reschedules its drain from the completion handler when a candidate arrived between the loop's last empty check and #processing being cleared, and settled() waits through restarts - #persistSnapshot releases the staging link after a failed publication only once the owned sidecar is confirmed withdrawn, so a rollback failure never leaves a singly linked sidecar for readers to adopt * ci: retrigger checks for the rebased head * chore: drop the portable byte-lane changes superseded by #406; keep the _meta assertions * fix(playground): restore the staging guard when a recovery fsync fails * fix(playground): accept a concurrently restored staging guard (EEXIST aliasing the sidecar) during recovery * fix(dev): recheck supersession after the adoption lease settles * fix(playground): keep a fresh pid-owned guard when recovery can neither re-link nor withdraw the sidecar * fix(playground): fsync every compensating recovery guard before trusting it
Summary
Slice 2 of #188 (Codex hook contract).
hooks/hooks.json(pinnedhooks.schema.json+ the inline hook document inplugin.schema.json) to the eleven release-documented events;Interruptstays indeferredNativeEventswith a dated reason (documented on learn.chatgpt.com, but rust-v0.147.0 ships no generated Interrupt schema and there is no canonical route).commandhandler field (commandWindows,timeout,statusMessage,additionalContextLimit,async) andmcp_toolhandlers (server,tool,input,timeout,statusMessage);prompt/agenthandlers (parsed-but-skipped by Codex) fail withcodex.native-hooks.handler.skipped.codex.native-hooks.event.{deferred,unknown},codex.hooks.session-end.{mcp-tool,async,timeout},codex.hooks.additional-context-limit.event,codex.hooks.matcher.ignored,codex.hook.tool.hosted(codex:WebSearch).model/permission_mode/turn_idas the generated schemas require; CodexStopaccepts a nulllast_assistant_messageasstop.command.inputdoes.hooks.contractcapability rows (13, four-state, dated evidence) mirrored ashook*adapter capabilities and intersected to honestunavailableon the unifiedplugintarget. Codex adapter 1.6.0 → 1.7.0, unified plugin 1.21.0 → 1.22.0.Evidence
rust-v0.147.0(21 files, no interrupt) andmain(interrupt present).tests/codex-hook-contract.test.ts(10 tests) plus updatedadapter-metadata.test.tspins (sha-checked upstream digests).pnpm typecheck✅,pnpm lint✅ (0 errors),pnpm test:unit✅ 2675 passed / 5 skipped,pnpm test:route-unit✅ 35,pnpm test:projection✅ 63,pnpm build && pnpm test:integration:run— 934/968 with 4 failures, all pre-existing/unrelated:host-install-proofCodexlogoexpectation (already fixed on main by fix(docs,test): closed-issue audit G4 — stale Claude cwd/preview-peer docs, restore rendered-skill docs, pin the #23 handshake queue #368; passes after rebase) and three workbench MCP App browser timeouts that pass when re-run in isolation.Test plan
pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/codex-hook-contract.test.ts packages/agent-bundle/tests/adapter-metadata.test.ts packages/agent-bundle/tests/codex-plugin-validation.test.ts packages/agent-bundle/tests/hooks.test.ts packages/agent-bundle/tests/host-adapters.test.ts