Summary
On the Codex host, CLX converts every ask verdict into a hard deny. Codex has no interactive approval path for hooks, so instead of prompting, the command is killed and the model sees:
Command blocked by PreToolUse hook: CLX requires manual approval; Codex does not support interactive ask - re-run if intended.. Command: <cmd>
That string is CLX's own — it lives in the Codex output adapter in clx-hook (crates/clx-hook/src/router.rs, in the serialize Codex decision / write Codex decision path); Codex only wraps it in Command blocked by PreToolUse hook: {reason}. Command: {cmd} (hence the doubled period).
The practical effect: since the L1 validator returns ask fairly readily, ordinary read-only commands become permanently unrunnable under Codex. - re-run if intended is misleading — re-running produces the identical deny, because nothing about the verdict is user-driven.
The escape hatch is the problem. No configuration knob prevents this. The only thing that works is clx trust on, which disables the validator entirely. A safety feature whose only recovery is "turn the safety feature off" inverts its own purpose — and that is what happened here: after a run of blocks, trust mode was enabled for 10 hours just to get work done.
Environment
|
|
| CLX |
0.12.0 (clx health: all green, 1 warn on validator prompt) |
| Codex CLI |
0.147.0 (@openai/codex, darwin-arm64) |
| OS |
macOS 15.5, arm64 |
| Hooks |
installed via clx install --target codex (~/.codex/hooks.json, PreToolUse + PermissionRequest) |
Relevant ~/.clx/config.yaml — note these are already the permissive settings:
validator:
default_decision: allow
on_validator_unavailable: ask
auto_allow_reads: true
prompt_sensitivity: low
trust_mode: false
Evidence
All rows below are verbatim from ~/.clx/data/clx.db → audit_log, filtered on host='codex'. Paths and hostnames are redacted.
Funnel 1 — L1 flags benign / read-only commands risk=5
2026-08-14T15:34:20Z layer=L1 decision=prompted risk=5
cmd: ssh-keygen -F '[bastion.example.com]:2222' -f ~/.ssh/known_hosts
reason: [caution] The command interacts with an existing file in ~/.ssh (known_hosts) ...
While it only reads the file to perform a lookup and does not modify or delete
anything, caution is warranted ...
The reasoning explicitly states the command only reads. auto_allow_reads: true did not catch it, and prompt_sensitivity: low did not lower it below the ask threshold. Same treatment for codex mcp get clx and codex mcp list — introspection commands, both risk=5, both blocked.
Funnel 2 — validator error degrades to ask
2026-08-14T15:23:06Z layer=L1 decision=prompted risk=5
reason: LLM response parsing failed
A transient parse failure becomes an unconditional block on Codex.
Funnel 3 — on_validator_unavailable: ask overrides an explicit allow
2026-07-31T22:31:08Z layer=L1 decision=prompted
reason: Ollama unavailable — effective_decision: ask (configured: allow)
The user configured allow; provider downtime silently upgrades it to ask, which on Codex is deny. So on Codex, default_decision: allow is not honored whenever the validator is unreachable.
Funnel 4 — L0-PERMREQ fail-closed
2026-08-14T15:37:53Z layer=L0-PERMREQ decision=blocked
reason: CLX could not positively clear this command; denying (fail closed). Re-run if intended.
cmd: codex mcp list ...
Same dead end, different layer, and the same misleading "re-run if intended".
Aftermath
2026-08-14T15:19:08Z ~/.clx/.trust_mode_token created, duration_secs=36000, enabled_by=cli
Immediately after the block sequence. Validator off for 10 hours across every host — Claude Code included, which was working fine.
Expected behavior
On a host with no interactive approval channel, an ask verdict should not silently become a permanent deny. Reasonable options, roughly in order of preference:
- A configurable ask-fallback per host, e.g.
validator.ask_fallback_when_noninteractive: allow | deny (default deny to preserve current behavior, but let users opt into allow rather than forcing all-or-nothing trust mode). This is the smallest change that removes the "disable the validator" cliff.
- Honor
default_decision when the validator is unavailable on non-interactive hosts, or at minimum make on_validator_unavailable host-aware — a provider outage should not be indistinguishable from a genuine risk finding.
- Fix the message.
- re-run if intended implies user action will change the outcome. It cannot. Say what it is: the verdict was ask, this host cannot prompt, and here is the knob (or clx trust on) to change it. Include the underlying reasoning string, which is currently dropped — the model gets no signal about why, so it cannot adapt.
- Tighten the read-only path.
ssh-keygen -F <host> -f <known_hosts> is a lookup; codex mcp list/get are introspection. With auto_allow_reads: true and prompt_sensitivity: low, these should clear L0 and never reach the LLM.
Reproduce
clx install --target codex; ensure clx trust status is off.
- In Codex, run
ssh-keygen -F '[bastion.example.com]:2222' -f ~/.ssh/known_hosts.
- The command is blocked, not prompted. Re-running reproduces it identically.
sqlite3 ~/.clx/data/clx.db "select layer,decision,risk_score,reasoning from audit_log where host='codex' order by timestamp desc limit 5" shows L1 / prompted / 5.
Happy to test a patch against this setup.
Summary
On the Codex host, CLX converts every
askverdict into a hard deny. Codex has no interactive approval path for hooks, so instead of prompting, the command is killed and the model sees:That string is CLX's own — it lives in the Codex output adapter in
clx-hook(crates/clx-hook/src/router.rs, in theserialize Codex decision/write Codex decisionpath); Codex only wraps it inCommand blocked by PreToolUse hook: {reason}. Command: {cmd}(hence the doubled period).The practical effect: since the L1 validator returns
askfairly readily, ordinary read-only commands become permanently unrunnable under Codex.- re-run if intendedis misleading — re-running produces the identical deny, because nothing about the verdict is user-driven.The escape hatch is the problem. No configuration knob prevents this. The only thing that works is
clx trust on, which disables the validator entirely. A safety feature whose only recovery is "turn the safety feature off" inverts its own purpose — and that is what happened here: after a run of blocks, trust mode was enabled for 10 hours just to get work done.Environment
clx health: all green, 1 warn on validator prompt)@openai/codex, darwin-arm64)clx install --target codex(~/.codex/hooks.json, PreToolUse + PermissionRequest)Relevant
~/.clx/config.yaml— note these are already the permissive settings:Evidence
All rows below are verbatim from
~/.clx/data/clx.db→audit_log, filtered onhost='codex'. Paths and hostnames are redacted.Funnel 1 — L1 flags benign / read-only commands
risk=5The reasoning explicitly states the command only reads.
auto_allow_reads: truedid not catch it, andprompt_sensitivity: lowdid not lower it below the ask threshold. Same treatment forcodex mcp get clxandcodex mcp list— introspection commands, bothrisk=5, both blocked.Funnel 2 — validator error degrades to
askA transient parse failure becomes an unconditional block on Codex.
Funnel 3 —
on_validator_unavailable: askoverrides an explicitallowThe user configured
allow; provider downtime silently upgrades it toask, which on Codex isdeny. So on Codex,default_decision: allowis not honored whenever the validator is unreachable.Funnel 4 —
L0-PERMREQfail-closedSame dead end, different layer, and the same misleading "re-run if intended".
Aftermath
Immediately after the block sequence. Validator off for 10 hours across every host — Claude Code included, which was working fine.
Expected behavior
On a host with no interactive approval channel, an
askverdict should not silently become a permanentdeny. Reasonable options, roughly in order of preference:validator.ask_fallback_when_noninteractive: allow | deny(defaultdenyto preserve current behavior, but let users opt intoallowrather than forcing all-or-nothing trust mode). This is the smallest change that removes the "disable the validator" cliff.default_decisionwhen the validator is unavailable on non-interactive hosts, or at minimum makeon_validator_unavailablehost-aware — a provider outage should not be indistinguishable from a genuine risk finding.- re-run if intendedimplies user action will change the outcome. It cannot. Say what it is: the verdict wasask, this host cannot prompt, and here is the knob (orclx trust on) to change it. Include the underlying reasoning string, which is currently dropped — the model gets no signal about why, so it cannot adapt.ssh-keygen -F <host> -f <known_hosts>is a lookup;codex mcp list/getare introspection. Withauto_allow_reads: trueandprompt_sensitivity: low, these should clear L0 and never reach the LLM.Reproduce
clx install --target codex; ensureclx trust statusis off.ssh-keygen -F '[bastion.example.com]:2222' -f ~/.ssh/known_hosts.sqlite3 ~/.clx/data/clx.db "select layer,decision,risk_score,reasoning from audit_log where host='codex' order by timestamp desc limit 5"showsL1 / prompted / 5.Happy to test a patch against this setup.