Skip to content

Codex host: every ask verdict becomes a hard deny, and the only escape is disabling the validator #80

Description

@blackaxgit

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. clx install --target codex; ensure clx trust status is off.
  2. In Codex, run ssh-keygen -F '[bastion.example.com]:2222' -f ~/.ssh/known_hosts.
  3. The command is blocked, not prompted. Re-running reproduces it identically.
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions