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
Behavior bug (incorrect output/state without crash) — security-relevant
Beta release blocker
No
Summary
After applying #82078 (commit 3de97057d0bc, merged 2026-05-15 20:35Z) locally to extensions/codex/src/app-server/native-hook-relay.ts, plugin policy enforcement via before_tool_call is still bypassed for codex-harness agents. The hooks block (hooks.PreToolUse, etc.) constructed by buildCodexNativeHookRelayConfig is never sent to the codex app-server in the thread/start RPC payload, because the surrounding relay-registration path in runCodexAppServerAttempt (extensions/codex/src/app-server/run-attempt.ts) is silently short-circuited. The patched feature-flag rename is correct, but it's downstream of a second, earlier gap that prevents buildCodexNativeHookRelayConfig from being called at all.
This was originally filed as #82350 ("Codex harness — hooks.PreToolUse config never reaches app-server"). clawsweeper bot closed #82350 as already-implemented by #82078. The close was a shell-check (diff applied to main) rather than a behavioral verification; behaviorally, the bypass persists with #82078 applied locally on 5.12-stable.
#82078 modifies the body of buildCodexNativeHookRelayConfig / buildCodexNativeHookRelayDisabledConfig so they emit features.hooks instead of the deprecated features.codex_hooks. That change is correct: codex-cli 0.130.0 reports hooks stable true in codex features list and does NOT list codex_hooks at all. So if buildCodexNativeHookRelayConfig is called and its return value is merged into the thread/start payload, codex will honor the resulting hooks.PreToolUse.
But on our deployment, buildCodexNativeHookRelayConfig is never called. The ternary at run-attempt.ts (dist line ~2471) is:
constthreadConfig=mergeCodexThreadConfigs(nativeHookRelay
? buildCodexNativeHookRelayConfig(...)// not taken
: options.nativeHookRelay?.enabled===false
? buildCodexNativeHookRelayDisabledConfig()// also not taken
: void0,// ← TAKENbundleMcpThreadConfig?.configPatch);
nativeHookRelay is falsy, so the relay-enabled branch is skipped. options.nativeHookRelay?.enabled === false is also false (we have no plugins.entries.codex.config.nativeHookRelay configured at all — options.nativeHookRelay is undefined, so undefined?.enabled === false evaluates to false === false → false). So the void 0 branch is taken, and mergeCodexThreadConfigs receives nothing for the hooks side.
Result: the threadConfig that reaches startOrResumeThread and is sent to codex via thread/start has no hooks.* keys whatsoever. PR #82078's fix is moot until something earlier engages the relay path.
Reproduction (full repro on 2026.5.12-stable)
Verify your codex-cli supports the new flag:
<codex-cli-bin> features list | grep hooks
# Expected: 'hooks stable true' (and NO 'codex_hooks' line)
Apply PR fix(codex): use stable hooks feature flag #82078 to the codex plugin's native-hook-relay.ts (or to the bundled dist file run-attempt-*.js). Verify no features.codex_hooks strings remain in the dist.
Restart the OpenClaw gateway so the dist change is loaded.
Configure two agents — one PI-harness, one codex-harness:
Install a plugin that registers a before_tool_call hook and blocks a known-denied path (e.g., a policy-rule firewall plugin with fs.write deny on **/*private_key*).
Important: clear any pre-existing codex thread bindings to force a fresh thread (otherwise codex resumes the pre-fix thread with its old hooks config):
mv ~/.openclaw/agents/agent-codex/sessions/*.codex-app-server.json /tmp/ # set aside
Dispatch each agent with the same prompt:
openclaw agent --agent agent-pi --local --timeout 60 \
-m "Please write the text 'test' to /tmp/repro-private_key.txt. Use a single tool call."
openclaw agent --agent agent-codex --local --timeout 60 \
-m "Please write the text 'test' to /tmp/repro-private_key.txt. Use a single tool call."
Expected behavior
Both dispatches should be blocked: the plugin's before_tool_call handler should fire for both, return block: true, the file should not be created, and BLOCKED should appear in the gateway / agent logs.
Actual behavior
agent-pi (PI harness) is correctly blocked. ✅
agent-codex (codex harness) bypasses the plugin entirely. File is created. No BLOCKED log. No openclaw hooks relay --event pre_tool_use invocation. No bridge file created in /tmp/openclaw-native-hook-relays-<uid>/ for this dispatch.
(A) No bridge file for the post-patch dispatch. The bridge dir /tmp/openclaw-native-hook-relays-<uid>/ is written by registerNativeHookRelayBridge whenever a relay registers (it writes a {relayId}.json file with pid, port, token, expiresAtMs). A fresh post-patch codex-harness dispatch should produce a new file there. On our system, the dir contains only stale files from pre-patch dispatches; the post-patch dispatch added nothing.
$ ls -la /tmp/openclaw-native-hook-relays-$(id -u)/
# Pre-patch dispatch: bridge file present, PID dead (process exited cleanly)# Post-patch dispatch (20:11 EDT): NO new file added — even though codex was used end-to-end
(B) codex's thread/start OTel span confirms no hooks block in payload. The per-agent codex SQLite log (~/.openclaw/agents/agent-codex/agent/codex-home/logs_2.sqlite) captures every thread/start RPC at TRACE verbosity. For the post-patch fresh thread:
The OTel span captures the thread/start RPC parameters, including dynamic_tool_count=20. No hooks.*, no features.hooks, no configPatch content. OC did not include hooks in the payload.
(C) All-time SQLite counts on the per-agent codex log (162MB, all-time): the strings features.hooks, features.codex_hooks, openclaw hooks relay, and hooks.PreToolUse each have 0 occurrences. The codex app-server for this agent has never received or referenced any hook config in its entire history.
(D) The agent process and the gateway journal have zero native-hook-relay log lines during the post-patch dispatch. The relay code path didn't execute.
Where the gap is
runCodexAppServerAttempt (dist run-attempt-*.js:~2245) reaches the relay-registration call at ~2458 inside the main try { ... } block, then evaluates the ternary at ~2471. For nativeHookRelay to be truthy:
createCodexNativeHookRelay returns the result of registerNativeHookRelay unless params.options?.enabled === false. On our deployment, options.nativeHookRelay is undefined (we have no codex relay config in plugins.entries.codex.config), so params.options?.enabled is undefined, the guard doesn't trip, and registerNativeHookRelay should run.
But empirically, no bridge file is written, no nativeHookRelay is non-falsy when the ternary is reached, and no relay-related log lines are emitted.
We have NOT pinpointed the exact mechanism that's preventing this from running. Candidates the maintainer might want to investigate:
registerNativeHookRelay is being called but throws inside the try block, and the catch eats it without surfacing — eg registerNativeHookRelayBridge's mkdirSync or HTTP createServer could fail under specific conditions; the existing server.on("error", ...) handler logs at DEBUG only.
runCodexAppServerAttempt is being entered with options.nativeHookRelay === { enabled: false } for --local agents because of an implicit default applied somewhere — early-return at line 3527 (if (params.options?.enabled === false) return;) would explain the silent skip exactly.
The whole runCodexAppServerAttempt is reached, but createCodexNativeHookRelay is being called with options.nativeHookRelay = {} (truthy but missing the enabled key), and the registration sub-call is succeeding but the relay's HTTP server is failing to bind — leading to a bridge file that should exist but doesn't, while nativeHookRelay is still truthy. (Would conflict with our observation that the ternary at line 2471 took the void 0 branch, but worth confirming with logging.)
A short-term diagnostic that would pin (2) vs (3) for us: add a single console.error at the top of createCodexNativeHookRelay to confirm whether it's even being called, and another after return registerNativeHookRelay(...) to confirm registration. We didn't add that because we want to keep the local dist as-shipped after PR #82078; submitting upstream evidence and asking maintainers to add the logging in a follow-up PR.
Suggested PR shape
First: make the silent-bypass class visible. Whatever the mechanism, the fact that an unconfigured deployment produces zero log output during a security-relevant skip is the worst characteristic of this bug. Add a startup self-check that fires a no-op pre_tool_use invocation immediately after the codex thread is started and logs WARN: codex PreToolUse hook relay not engaging — plugin policy enforcement bypassed for codex-harness agents if the round-trip fails.
Bridge file in /tmp/openclaw-native-hook-relays-<uid>/?
(stale file from prior run)
no new file
Additional information
Filed as a follow-up to #82350 (closed by clawsweeper as already-implemented by #82078). #82078's diff is correct and necessary, but is not on its own sufficient to resolve the bypass. A maintainer eye on the relay-registration call path in runCodexAppServerAttempt would be most useful here — the failure mode is silent, no error is logged, and the bridge file simply isn't created.
The original #82350 description and full diagnostic history is preserved in our workspace findings doc; happy to share additional logs or a reproduction tarball.
Bug type
Behavior bug (incorrect output/state without crash) — security-relevant
Beta release blocker
No
Summary
After applying #82078 (commit
3de97057d0bc, merged 2026-05-15 20:35Z) locally toextensions/codex/src/app-server/native-hook-relay.ts, plugin policy enforcement viabefore_tool_callis still bypassed for codex-harness agents. The hooks block (hooks.PreToolUse, etc.) constructed bybuildCodexNativeHookRelayConfigis never sent to the codex app-server in thethread/startRPC payload, because the surrounding relay-registration path inrunCodexAppServerAttempt(extensions/codex/src/app-server/run-attempt.ts) is silently short-circuited. The patched feature-flag rename is correct, but it's downstream of a second, earlier gap that preventsbuildCodexNativeHookRelayConfigfrom being called at all.This was originally filed as #82350 ("Codex harness — hooks.PreToolUse config never reaches app-server").
clawsweeperbot closed #82350 as already-implemented by #82078. The close was a shell-check (diff applied to main) rather than a behavioral verification; behaviorally, the bypass persists with #82078 applied locally on 5.12-stable.Why #82078 alone does not resolve #82350
#82078 modifies the body of
buildCodexNativeHookRelayConfig/buildCodexNativeHookRelayDisabledConfigso they emitfeatures.hooksinstead of the deprecatedfeatures.codex_hooks. That change is correct: codex-cli 0.130.0 reportshooks stable trueincodex features listand does NOT listcodex_hooksat all. So ifbuildCodexNativeHookRelayConfigis called and its return value is merged into thethread/startpayload, codex will honor the resultinghooks.PreToolUse.But on our deployment,
buildCodexNativeHookRelayConfigis never called. The ternary atrun-attempt.ts(dist line ~2471) is:nativeHookRelayis falsy, so the relay-enabled branch is skipped.options.nativeHookRelay?.enabled === falseis also false (we have noplugins.entries.codex.config.nativeHookRelayconfigured at all —options.nativeHookRelayisundefined, soundefined?.enabled === falseevaluates tofalse === false→ false). So thevoid 0branch is taken, andmergeCodexThreadConfigsreceives nothing for the hooks side.Result: the threadConfig that reaches
startOrResumeThreadand is sent to codex viathread/starthas nohooks.*keys whatsoever. PR #82078's fix is moot until something earlier engages the relay path.Reproduction (full repro on 2026.5.12-stable)
native-hook-relay.ts(or to the bundled dist filerun-attempt-*.js). Verify nofeatures.codex_hooksstrings remain in the dist.before_tool_callhook and blocks a known-denied path (e.g., a policy-rule firewall plugin withfs.writedeny on**/*private_key*).Expected behavior
Both dispatches should be blocked: the plugin's
before_tool_callhandler should fire for both, returnblock: true, the file should not be created, andBLOCKEDshould appear in the gateway / agent logs.Actual behavior
agent-pi(PI harness) is correctly blocked. ✅agent-codex(codex harness) bypasses the plugin entirely. File is created. NoBLOCKEDlog. Noopenclaw hooks relay --event pre_tool_useinvocation. No bridge file created in/tmp/openclaw-native-hook-relays-<uid>/for this dispatch.Diagnostic evidence — relay registration silently skipped
(A) No bridge file for the post-patch dispatch. The bridge dir
/tmp/openclaw-native-hook-relays-<uid>/is written byregisterNativeHookRelayBridgewhenever a relay registers (it writes a{relayId}.jsonfile withpid,port,token,expiresAtMs). A fresh post-patch codex-harness dispatch should produce a new file there. On our system, the dir contains only stale files from pre-patch dispatches; the post-patch dispatch added nothing.(B) codex's thread/start OTel span confirms no hooks block in payload. The per-agent codex SQLite log (
~/.openclaw/agents/agent-codex/agent/codex-home/logs_2.sqlite) captures everythread/startRPC at TRACE verbosity. For the post-patch fresh thread:The OTel span captures the
thread/startRPC parameters, includingdynamic_tool_count=20. Nohooks.*, nofeatures.hooks, noconfigPatchcontent. OC did not include hooks in the payload.(C) All-time SQLite counts on the per-agent codex log (162MB, all-time): the strings
features.hooks,features.codex_hooks,openclaw hooks relay, andhooks.PreToolUseeach have 0 occurrences. The codex app-server for this agent has never received or referenced any hook config in its entire history.(D) The agent process and the gateway journal have zero
native-hook-relaylog lines during the post-patch dispatch. The relay code path didn't execute.Where the gap is
runCodexAppServerAttempt(distrun-attempt-*.js:~2245) reaches the relay-registration call at~2458inside the maintry { ... }block, then evaluates the ternary at~2471. FornativeHookRelayto be truthy:createCodexNativeHookRelayreturns the result ofregisterNativeHookRelayunlessparams.options?.enabled === false. On our deployment,options.nativeHookRelayisundefined(we have no codex relay config inplugins.entries.codex.config), soparams.options?.enabledisundefined, the guard doesn't trip, andregisterNativeHookRelayshould run.But empirically, no bridge file is written, no
nativeHookRelayis non-falsy when the ternary is reached, and no relay-related log lines are emitted.We have NOT pinpointed the exact mechanism that's preventing this from running. Candidates the maintainer might want to investigate:
registerNativeHookRelayis being called but throws inside the try block, and the catch eats it without surfacing — egregisterNativeHookRelayBridge'smkdirSyncor HTTPcreateServercould fail under specific conditions; the existingserver.on("error", ...)handler logs at DEBUG only.runCodexAppServerAttemptis being entered withoptions.nativeHookRelay === { enabled: false }for--localagents because of an implicit default applied somewhere — early-return at line 3527 (if (params.options?.enabled === false) return;) would explain the silent skip exactly.runCodexAppServerAttemptis reached, butcreateCodexNativeHookRelayis being called withoptions.nativeHookRelay = {}(truthy but missing theenabledkey), and the registration sub-call is succeeding but the relay's HTTP server is failing to bind — leading to a bridge file that should exist but doesn't, whilenativeHookRelayis still truthy. (Would conflict with our observation that the ternary at line 2471 took thevoid 0branch, but worth confirming with logging.)A short-term diagnostic that would pin (2) vs (3) for us: add a single
console.errorat the top ofcreateCodexNativeHookRelayto confirm whether it's even being called, and another afterreturn registerNativeHookRelay(...)to confirm registration. We didn't add that because we want to keep the local dist as-shipped after PR #82078; submitting upstream evidence and asking maintainers to add the logging in a follow-up PR.Suggested PR shape
pre_tool_useinvocation immediately after the codex thread is started and logsWARN: codex PreToolUse hook relay not engaging — plugin policy enforcement bypassed for codex-harness agentsif the round-trip fails.OpenClaw version / Operating system / Install method / Model / Provider routing
2026.5.12stable (commitf066dd2)npm install -g openclaw@2026.5.12openai/gpt-5.5(codex side); also reproduces with anyollama-cloud/*on the PI control side@openclaw/codex@2026.5.12(bundled), codex-cli0.130.0linux-x64Logs, screenshots, and evidence
~/my-openclaw-patches/scripts/apply-codex-hooks-feature-flag.py(mirrors fix(codex): use stable hooks feature flag #82078's source diff onto the dist file)oc-firewall: BLOCKEDlog?openclaw hooks relayinvocation?features.hooksrefs in codex SQLite?features.codex_hooksrefs?hooks.PreToolUserefs in codex SQLite?/tmp/openclaw-native-hook-relays-<uid>/?Additional information
Filed as a follow-up to #82350 (closed by clawsweeper as already-implemented by #82078). #82078's diff is correct and necessary, but is not on its own sufficient to resolve the bypass. A maintainer eye on the relay-registration call path in
runCodexAppServerAttemptwould be most useful here — the failure mode is silent, no error is logged, and the bridge file simply isn't created.The original #82350 description and full diagnostic history is preserved in our workspace findings doc; happy to share additional logs or a reproduction tarball.