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)
Beta release blocker
No (but security-relevant — see Impact)
Summary
The hooks.PreToolUse config that the bundled codex plugin builds in buildCodexNativeHookRelayConfig (extensions/codex/src/app-server/native-hook-relay.ts) is not reaching codex's app-server in the thread/start RPC payload. As a result, every pre_tool_use event that should bridge through openclaw hooks relay back to plugin before_tool_call handlers never fires. Plugin policy enforcement (e.g., oc-firewall-style fs.write / shell.exec denials) is silently inert for any agent running through the codex harness, while the same plugin enforces correctly for agents on the PI harness.
This is the same failure-mode family as #76201 ("Plugin before_tool_call hook does not fire for native exec on 2026.4.29 (Anthropic harness)"), but observed on a different harness (codex) on a different version (2026.5.12 stable). #76201's hypothesis (getGlobalHookRunner() returning null in the native-exec dispatch path) does not appear to be the root cause here; codex never receives the hook config in the first place.
Steps to reproduce
Install OpenClaw 2026.5.12 (stable) on Linux with the bundled codex plugin enabled. Sign in via openclaw models auth login --provider openai-codex.
Create two agents — one routed through PI, one through codex:
// openclaw.json"agents": {"list": [{"id": "agent-pi","model": {"primary": "ollama-cloud/glm-5.1"}// any non-openai model → PI harness},{"id": "agent-codex","model": "openai/gpt-5.5"// → codex harness}]}
Install a plugin that registers a before_tool_call handler and blocks a known-denied path. Minimal reproduction: any plugin doing api.on("before_tool_call", ...) and calling an authorization sidecar. A practical example is a policy-rule firewall plugin with an fs.write deny rule for **/*private_key*:
api.on("before_tool_call",async(event,ctx)=>{if(event.toolName==="bash"||event.toolName==="Bash"||event.toolName==="exec"){constcmd=event.params?.command??"";if(cmd.includes("private_key"))return{block: true,blockReason: "denied by policy"};}},{priority: 100});
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."
Observe outcomes.
Expected behavior
Both dispatches should be blocked. The plugin's before_tool_call handler should fire for both, return block: true, and the test file should not be created. The codex agent's shell-out (bash tool calling printf ... > /tmp/repro-private_key.txt) should bridge through openclaw hooks relay --event pre_tool_use (configured into the thread's hooks.PreToolUse by the codex plugin), invoking the plugin handler and producing a deny.
Actual behavior
agent-pi (PI harness) is correctly blocked. Plugin logs BLOCKED ...; the file is not created.
agent-codex (codex harness) bypasses the plugin entirely. No before_tool_call handler invocation. No openclaw hooks relay shell-out. The file is successfully written.
The model on the codex side invokes the bash tool (codex's internal exec_command exposed through the openai-responses API) with a /bin/bash -lc "printf '%s' 'test' > /tmp/repro-private_key.txt" command. Codex's app-server runs the command directly without firing any PreToolUse hook.
Diagnostic evidence — codex never receives the hooks config
Codex's app-server maintains a local SQLite trace DB at ~/.codex/logs_2.sqlite (table: logs(ts, level, target, feedback_log_body, ...)) at full verbosity. Snapshotting a DB containing 110,733 log rows spanning 5.8 days (INFO 54%, TRACE 42%, DEBUG 2%, WARN+ERROR ~1.5%), across a window covering several codex-harness dispatches:
Search term
Match count in 110K rows
PreToolUse
0
PostToolUse
0
UserPromptSubmit
0
pre_tool_use
0
post_tool_use
0
hooks.Stop
0
For control: the same DB has 16 %thread%start% matches (thread-start RPC tracing), 28,476 codex_api::endpoint::responses_websocket rows, and 110 codex_app_server::outgoing_message rows — so codex IS tracing its RPC and message flows at the captured verbosity. It just never sees any hooks.* payload.
~/.codex/config.toml (persistent user config) also has no [hooks] section. The hooks block is supposed to arrive via the thread/start RPC's configPatch, but evidently doesn't.
Probable transport-point culprits
The hooks.PreToolUse config is constructed at extensions/codex/src/app-server/native-hook-relay.ts:buildCodexNativeHookRelayConfig (visible in dist as run-attempt-*.js around the CODEX_NATIVE_HOOK_RELAY_EVENTS = ["pre_tool_use", "post_tool_use", "permission_request", "before_agent_finalize"] block). It then flows through:
nativeHookRelay = createCodexNativeHookRelay({ options: options.nativeHookRelay, ... })
↓ early-returns undefined if options?.enabled === false (and possibly other paths)
const threadConfig = mergeCodexThreadConfigs(
nativeHookRelay ? buildCodexNativeHookRelayConfig(...) : ...,
bundleMcpThreadConfig?.configPatch
)
↓ threadConfig passed to startOrResumeThread as params.config
↓ startOrResumeThread sends it via thread/start JSON-RPC to codex app-server
Most plausible drop points (not yet pinpointed):
createCodexNativeHookRelay returning undefined for a reason other than the documented enabled === false (e.g., if registerNativeHookRelay throws under some condition, the relay would silently be undefined, and the hooks block would never be constructed).
mergeCodexThreadConfigs dropping keys from its first argument under some condition.
The thread/start JSON-RPC serialization stripping the hooks field — less likely but possible if codex's app-server schema rejects extra keys.
There's also a visible Pre/Post asymmetry in the codex plugin's projection layer: CodexAppServerEventProjector references nativePostToolUseRelayEnabled for post-tool-use deduplication, but there's no nativePreToolUseRelayEnabled equivalent. The pre-side receiver in agents/harness/native-hook-relay.ts (runNativeHookRelayPreToolUse, renderPreToolUseBlockResponse) exists, but the codex-projection layer that should wire to fire it is one-eyed. This may or may not be load-bearing for the transport bug — would benefit from maintainer eyes.
Impact
Security-relevant. Any plugin relying on before_tool_call for tool-call policy enforcement (firewall plugins, audit-trail plugins, sandboxing plugins, approval-gate plugins, prompt-injection-shield plugins, …) is silently inert for every codex-harness agent. The plugin's register() runs, logs success, and produces no enforcement at runtime for any codex-routed tool call.
For configurations where openai/gpt-* agents are the production primary (common deployment pattern since 5.0+), every fs.* and shell.exec operation by those agents bypasses the plugin layer. Operators have no visible signal that this is happening — the plugin is loaded, the policy is correct, the sidecar (where applicable) returns correct decisions when consulted; it's the consultation that never occurs.
Suggested fixes (in order of cheapness)
Visible-failure step (cheap, ships independently): Add a startup self-test that fires a no-op pre_tool_use invocation immediately after createCodexNativeHookRelay registration. If the round-trip fails, log a clear WARN at gateway log: codex: PreToolUse hook relay not engaging — plugin enforcement bypassed for codex-routed agents. Makes the silent-bypass class visible rather than invisible.
Diagnostic logging in the transport path: Add DEBUG logging at the three transport points (createCodexNativeHookRelay return, mergeCodexThreadConfigs input/output, the thread/start RPC body before send). This narrows down where the hooks block is dropped.
Actual fix (depends on what (2) reveals): likely one of the three drop points above.
Pre/Post symmetry fix: match the nativePostToolUseRelayEnabled pattern on the pre-side in the projector — may not be load-bearing but should be done either way for consistency.
OpenClaw version
2026.5.12 (stable, commit f066dd2)
Operating system
Linux (WSL2 Ubuntu)
Install method
npm install -g openclaw@2026.5.12
Model
openai/gpt-5.5 (codex harness side); also reproduced with any ollama-cloud/* model on the PI harness side as control
Provider / routing chain
openai-codex → bundled codex plugin → codex app-server (@openai/codex linux x64, started by codex plugin)
Logs, screenshots, and evidence
Plugin log line during a codex-harness bypass (file written, no block):
Plugin log line during a PI-harness control (file not written, block fires):
[plugins] oc-firewall: BLOCKED fs.write on /tmp/repro-private_key.txt for agent:agent-pi: explicit_deny
The `write` tool call was denied by the OC Firewall policy.
Codex app-server SQLite trace DB queries (full counts above).
Additional information
This issue is filed as a sibling to #76201, which observed the same failure-mode class on the Anthropic harness on 2026.4.29. Posting separately because:
Codex is the dominant production path for OpenAI agents in 5.x deployments, so the impact and fix scope are likely different from the Anthropic-harness case.
Happy to provide an end-to-end repro tarball if useful.
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No (but security-relevant — see Impact)
Summary
The
hooks.PreToolUseconfig that the bundledcodexplugin builds inbuildCodexNativeHookRelayConfig(extensions/codex/src/app-server/native-hook-relay.ts) is not reaching codex's app-server in thethread/startRPC payload. As a result, everypre_tool_useevent that should bridge throughopenclaw hooks relayback to pluginbefore_tool_callhandlers never fires. Plugin policy enforcement (e.g.,oc-firewall-stylefs.write/shell.execdenials) is silently inert for any agent running through the codex harness, while the same plugin enforces correctly for agents on the PI harness.This is the same failure-mode family as #76201 ("Plugin
before_tool_callhook does not fire for native exec on 2026.4.29 (Anthropic harness)"), but observed on a different harness (codex) on a different version (2026.5.12stable). #76201's hypothesis (getGlobalHookRunner()returning null in the native-exec dispatch path) does not appear to be the root cause here; codex never receives the hook config in the first place.Steps to reproduce
2026.5.12(stable) on Linux with the bundledcodexplugin enabled. Sign in viaopenclaw models auth login --provider openai-codex.before_tool_callhandler and blocks a known-denied path. Minimal reproduction: any plugin doingapi.on("before_tool_call", ...)and calling an authorization sidecar. A practical example is a policy-rule firewall plugin with anfs.writedeny rule for**/*private_key*:Expected behavior
Both dispatches should be blocked. The plugin's
before_tool_callhandler should fire for both, returnblock: true, and the test file should not be created. The codex agent's shell-out (bashtool callingprintf ... > /tmp/repro-private_key.txt) should bridge throughopenclaw hooks relay --event pre_tool_use(configured into the thread'shooks.PreToolUseby the codex plugin), invoking the plugin handler and producing a deny.Actual behavior
agent-pi(PI harness) is correctly blocked. Plugin logsBLOCKED ...; the file is not created.agent-codex(codex harness) bypasses the plugin entirely. Nobefore_tool_callhandler invocation. Noopenclaw hooks relayshell-out. The file is successfully written.The model on the codex side invokes the
bashtool (codex's internalexec_commandexposed through the openai-responses API) with a/bin/bash -lc "printf '%s' 'test' > /tmp/repro-private_key.txt"command. Codex's app-server runs the command directly without firing anyPreToolUsehook.Diagnostic evidence — codex never receives the hooks config
Codex's app-server maintains a local SQLite trace DB at
~/.codex/logs_2.sqlite(table:logs(ts, level, target, feedback_log_body, ...)) at full verbosity. Snapshotting a DB containing 110,733 log rows spanning 5.8 days (INFO 54%, TRACE 42%, DEBUG 2%, WARN+ERROR ~1.5%), across a window covering several codex-harness dispatches:PreToolUsePostToolUseUserPromptSubmitpre_tool_usepost_tool_usehooks.StopFor control: the same DB has 16
%thread%start%matches (thread-start RPC tracing), 28,476codex_api::endpoint::responses_websocketrows, and 110codex_app_server::outgoing_messagerows — so codex IS tracing its RPC and message flows at the captured verbosity. It just never sees anyhooks.*payload.~/.codex/config.toml(persistent user config) also has no[hooks]section. The hooks block is supposed to arrive via thethread/startRPC'sconfigPatch, but evidently doesn't.Probable transport-point culprits
The
hooks.PreToolUseconfig is constructed atextensions/codex/src/app-server/native-hook-relay.ts:buildCodexNativeHookRelayConfig(visible in dist asrun-attempt-*.jsaround theCODEX_NATIVE_HOOK_RELAY_EVENTS = ["pre_tool_use", "post_tool_use", "permission_request", "before_agent_finalize"]block). It then flows through:Most plausible drop points (not yet pinpointed):
createCodexNativeHookRelayreturningundefinedfor a reason other than the documentedenabled === false(e.g., ifregisterNativeHookRelaythrows under some condition, the relay would silently be undefined, and the hooks block would never be constructed).mergeCodexThreadConfigsdropping keys from its first argument under some condition.thread/startJSON-RPC serialization stripping the hooks field — less likely but possible if codex's app-server schema rejects extra keys.There's also a visible Pre/Post asymmetry in the codex plugin's projection layer:
CodexAppServerEventProjectorreferencesnativePostToolUseRelayEnabledfor post-tool-use deduplication, but there's nonativePreToolUseRelayEnabledequivalent. The pre-side receiver inagents/harness/native-hook-relay.ts(runNativeHookRelayPreToolUse,renderPreToolUseBlockResponse) exists, but the codex-projection layer that should wire to fire it is one-eyed. This may or may not be load-bearing for the transport bug — would benefit from maintainer eyes.Impact
Security-relevant. Any plugin relying on
before_tool_callfor tool-call policy enforcement (firewall plugins, audit-trail plugins, sandboxing plugins, approval-gate plugins, prompt-injection-shield plugins, …) is silently inert for every codex-harness agent. The plugin'sregister()runs, logs success, and produces no enforcement at runtime for any codex-routed tool call.For configurations where
openai/gpt-*agents are the production primary (common deployment pattern since 5.0+), everyfs.*andshell.execoperation by those agents bypasses the plugin layer. Operators have no visible signal that this is happening — the plugin is loaded, the policy is correct, the sidecar (where applicable) returns correct decisions when consulted; it's the consultation that never occurs.Suggested fixes (in order of cheapness)
pre_tool_useinvocation immediately aftercreateCodexNativeHookRelayregistration. If the round-trip fails, log a clear WARN at gateway log:codex: PreToolUse hook relay not engaging — plugin enforcement bypassed for codex-routed agents. Makes the silent-bypass class visible rather than invisible.createCodexNativeHookRelayreturn,mergeCodexThreadConfigsinput/output, thethread/startRPC body before send). This narrows down where the hooks block is dropped.nativePostToolUseRelayEnabledpattern on the pre-side in the projector — may not be load-bearing but should be done either way for consistency.OpenClaw version
2026.5.12(stable, commitf066dd2)Operating system
Linux (WSL2 Ubuntu)
Install method
npm install -g openclaw@2026.5.12Model
openai/gpt-5.5(codex harness side); also reproduced with anyollama-cloud/*model on the PI harness side as controlProvider / routing chain
openai-codex→ bundledcodexplugin → codex app-server (@openai/codexlinux x64, started by codex plugin)Logs, screenshots, and evidence
Plugin log line during a codex-harness bypass (file written, no block):
Plugin log line during a PI-harness control (file not written, block fires):
Codex app-server SQLite trace DB queries (full counts above).
Additional information
This issue is filed as a sibling to #76201, which observed the same failure-mode class on the Anthropic harness on 2026.4.29. Posting separately because:
before_tool_callhook does not fire for native exec on 2026.4.29 (Anthropic harness) #76201 hypothesizedgetGlobalHookRunner()returning null in the native-exec dispatch path; the codex evidence here shows the config never reaches the harness in the first place, so the bug is upstream of [Bug]: Pluginbefore_tool_callhook does not fire for native exec on 2026.4.29 (Anthropic harness) #76201's hypothesized point.Happy to provide an end-to-end repro tarball if useful.