fix(codex): cover side-question native hooks#82559
Conversation
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: yes. source-reproducible: current main leaves public Codex Real behavior proof Next step before merge Security Review detailsBest possible solution: Land the scoped relay hardening after maintainer review and redacted live runtime evidence showing Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: current main leaves public Codex Is this the best way to solve the issue? Yes on code direction: the PR extends the existing native hook relay seam and fails closed for unsafe approval-bridge outcomes. Merge should still wait for real runtime proof because this is a security-sensitive execution boundary. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against ee492092a71d. |
ab19e92 to
0adce0b
Compare
|
CI triage note for the current PR run on head The Node failures do not appear to be caused by this PR. I compared the failing PR jobs against the exact base commit used for the merge test,
The The PR-specific changed-surface validation remains green: remote GCP |
3397fba to
fd44d16
Compare
|
Superseded by the latest rebase/push. Current head: The earlier failures were tied to the previous stale/red base. After rebasing off that base and pushing the refreshed head, the PR check rollup is now green and GitHub reports the PR as |
fd44d16 to
7f620ee
Compare
|
Size note for reviewers: this PR is now labeled XL, and I understand that is a meaningful review burden. The raw diff size is mostly regression coverage rather than runtime code. At current head The reason I kept these pieces together is that they are one enforcement boundary: native PreToolUse policy should cover Codex command execution even when the command originates from the app-server approval path or a
That said, I am happy to split this if maintainers prefer smaller review units. The clean split would be:
The current PR is green and keeps the full behavior covered in one place, but I do not want the XL size to get in the way of review if a split would be easier. |
a710a83 to
2d717e7
Compare
117340b to
40fd348
Compare
|
Behavior addressed: /btw side-question native hooks and app-server command approvals now use the Codex native hook relay safely; unsupported rewrites fail closed; channel ids are preserved for side-question policy. Real environment tested: local source checkout plus GitHub Actions on PR head 40fd348. Exact steps or command run after this patch:
Evidence after fix: focused local regression/type/deadcode checks passed; Codex review reported no actionable findings; GitHub PR checks reported no failing, pending, or in-progress checks. Observed result after fix: native hook relay context is threaded through Codex app-server runs and /btw side questions, app-server command approvals use the active relay path, and the stale deadcode allowlist entry was removed after rebasing onto current main. What was not tested: no live model-driven /btw shell execution; covered by app-server/native relay unit tests and CI. Codex review sandbox could not run loopback bridge tests because its sandbox rejects 127.0.0.1 listen with EPERM. |
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(codex): cover side-question native hooks * fix(codex): enforce native approvals for app-server requests * fix(codex): preserve approval fallback after native relay noop * fix(codex): satisfy approval relay json typing * fix(codex): run approval relay in report mode * fix(codex): keep relay pre-tool decisions deny-only * fix(codex): remove dead relay approval branch * fix(codex): dedupe app-server relay approvals * fix(codex): fail closed on native relay rewrites * fix(codex): preserve side-question provider context * fix(codex): route side-question replies to origin * fix(codex): preserve native hook channel context * test(codex): align native relay rewrite assertion * fix(codex): align side-question hook config * fix(codex): route side-question approvals safely * test(codex): fix side-question hook typing * fix(codex): preserve side-question hook policy context * fix(codex): close native hook relay review gaps * fix(codex): keep dynamic tool hook channel context * fix(codex): preserve native finalize hook channel context * fix(codex): scope dynamic tool result hooks by channel * fix(codex): drop stale deadcode allowlist entry --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
Problem
#82496 restored Codex native hook relay coverage for the normal app-server run path, but the public Codex
/btwpath forks a child Codex thread throughrunCodexAppServerSideQuestion. That child thread needs its own hook config and its app-server approval requests need to reach the same native relay policy surface.There is also a related app-server approval gap: Codex native command approvals arrive as
item/commandExecution/requestApproval, while OpenClaw policy is expressed as PreToolUse/before-tool-call policy. Without bridging that approval request through the active native relay, a Codex app-server command approval can bypass the same native PreToolUse enforcement the main hook path relies on.Change and value
This PR keeps the existing native hook relay model and applies it to the missing surfaces:
/btwside-question runs now opt into native hook relay config.thread/forkandturn/start, preserving code-mode config.nativeHook.invokefor command approvals before prompting. Native deny fails closed to Codex; explicit native allow can accept; native no-decision falls through to the existing plugin approval route without re-running before-tool-call policy.exec_commandplustool_input.cmdnow normalizes to OpenClawexecpluscommandbefore policy evaluation.Who is affected, and who is not
Affected: Codex app-server users who depend on OpenClaw native hook relay policy for native Codex command execution, including
/btwside conversations.Not affected: non-Codex harnesses, internal direct side-question callers that do not opt into
nativeHookRelay, and Codex native no-decision approval behavior. A no-op PreToolUse response is not treated as an approval; it still falls through to the existing app-server approval flow.Implementation
runCodexAppServerSideQuestionnow registers a relay, merges hook config with Codex runtime config, sends it to the side thread, passes the relay to approval handling, and unregisters it in cleanup. The main run path now also passes its active relay into the shared approval bridge.handleCodexAppServerApprovalRequestnow maps app-server command approval requests into a Codex PreToolUse relay payload. It fails closed when the expected relay cannot be invoked, rejects native denials before prompting, accepts only explicit native allow decisions, and preserves the previous plugin approval path for native no-decision.Real behavior proof
Behavior or issue addressed: Codex
/btwside-question child threads and app-server command approval requests now reach OpenClaw native PreToolUse policy instead of relying only on the main-run hook config.Real environment tested: Local PR worktree
/tmp/openclaw-pr-side-question-hooksat head7f620eef8bb340ebac41dbe3650102478dc49e20, rebased onto the latest completed green fullmainCI base available at the time,89532d3a92a8bec0121ce954b682e052c3be2f42(CIrun25977237869, success). GitHub PR readback after the push showed current base54c9820ed9c220180b9cc2785b2b9921bc12b46f, with hosted PR CI running for head7f620eef8bb340ebac41dbe3650102478dc49e20.Exact steps or command run after this patch:
pnpm test extensions/codex/src/app-server/approval-bridge.test.ts extensions/codex/src/app-server/side-question.test.ts extensions/codex/index.test.ts src/agents/harness/native-hook-relay.test.ts src/agents/tool-policy.test.tsgit diff --checkEvidence after fix:
vitest.unit-fast: 31 passed.vitest.agents: 49 passed. It verifies Codex native hook relay behavior, including failing closed when native PreToolUse policy rewrites params and Codexexec_command/cmdinput normalization before OpenClaw policy evaluation.vitest.extensions: 3 files passed, 57 tests. It verifies native deny returnsdeclinebefore plugin prompting, explicit native allow returnsaccept, missing relay fails closed, native no-decision falls through to plugin approval without re-running legacy before-tool-call policy, side-question hook config on fork and turn start, side-thread approval forwarding with the active relay handle, event selection, disabled clearing config, TTL sizing, cleanup after success/failure, and the public Codex harness opting side questions intonativeHookRelay: { enabled: true }.git diff --check: passed.Observed result after fix: Side-question child threads receive native hook relay config, and Codex app-server command approval requests are checked through the active native relay before any app-server/plugin approval path can allow them.
What was not tested: I did not claim live
/btwshell execution proof from model-driven prompts because the local runtime attempts completed without producing an actual Codex shell tool call in logs. The broad local CI matrix was not run on this WSL host; hosted PR CI was retriggered on the pushed head.Verification
git diff --check## Real behavior proof, refreshed after the latest rebase at head7f620eef8bb340ebac41dbe3650102478dc49e20.