Skip to content

codex-native: per-session CODEX_HOME invalidates hook trust, so headless sub-agents never start #3268

Description

@sonsay

Summary

codex-native sub-agents can never start when the user has Codex hooks configured. The per-session CODEX_HOME breaks Codex's hook-trust records, so Codex opens an interactive "Hooks need review" prompt on every launch. In a headless sub-agent there is nobody to answer it, the app-server never emits thread/started, and the run dies on the 15s timeout.

This also affects the bundled polly orchestrator: its codex sub-agent is declared as codex-native, so cross-vendor review — polly's core guarantee — silently never runs on any machine where Codex hooks are configured.

Impact

Observed while running polly on a real task. Polly dispatched a codex reviewer twice; both attempts produced a one-item conversation and this error:

inner executor error: Codex native thread never started: Codex app-server never
started a thread (startup timed out: TimeoutError). See the runner log near
'native-codex routing' for the resolved provider/model.

Polly correctly reported that it could not obtain cross-vendor review and refused to merge — but the underlying cause is not visible from the error text, which points at provider/model routing rather than at the hook prompt.

Root cause

Codex keys hook-trust records by the absolute path of the file that declares the hook:

[hooks.state."/home/<user>/.codex/hooks.json:pre_tool_use:0:0"]
trusted_hash = "sha256:…"

codex-native provisions a private per-session CODEX_HOME
(~/.omnigent/codex-native/<session-id>/codex-home/) and copies config.toml
and hooks.json into it (_populate_codex_home_config, _CODEX_HOME_COPY_FILES).
The copied hooks.state keys still reference the original paths, while the
hooks are now loaded from the new path. Every key therefore misses, and
Codex classifies all hooks as new or changed.

Verified on a live install — trust keys inside one session's private home:

  4 entries → /home/<user>/.codex/config.toml            (global, not used here)
  9 entries → /home/<user>/.codex/hooks.json             (global, not used here)
  3 entries → /home/<user>/.omnigent/codex-native/<other-session-id>/codex-home/hooks.json

The third group is trust granted interactively during an earlier session: it
carries that session's id, so the next session — with a fresh id — misses again.
Trust granted through the TUI is written into the ephemeral home and discarded
with it, so the prompt reappears on every single launch and can never be
satisfied for headless runs.

Reproduction

  1. Configure any Codex hook, e.g. ~/.codex/hooks.json with a SessionStart entry.
  2. Run omnigent codex once and choose Trust all and continue. Codex starts normally.
  3. Run omnigent codex again → "Hooks need review — N hooks are new or changed" appears again.
  4. Ask polly to dispatch a codex sub-agent → Codex native thread never started … startup timed out.

Step 3 is the minimal reproduction; step 4 is the user-visible failure.

Proposed fix

When materialising the private CODEX_HOME, rewrite hooks.state keys so the
path component points at the copied files instead of the originals:

[hooks.state."<global home>/hooks.json:pre_tool_use:0:0"]
→ [hooks.state."<private home>/hooks.json:pre_tool_use:0:0"]

The hash component stays untouched, so trust is neither widened nor weakened —
it is only carried across the copy that Omnigent itself performs. Users who
already trust their hooks globally keep working; users who do not are still
prompted, as today.

Two smaller points, if useful:

  • The timeout message could name the real blocker. _CODEX_THREAD_START_TIMEOUT_SECONDS = 15.0
    in codex_native.py is hit whenever the TUI is waiting on any interactive
    prompt; pointing only at provider/model routing sends the reader in the wrong
    direction. (I first misdiagnosed this as MCP startup cost — with 21 MCP servers
    configured — and only found the real cause by attaching to the terminal.)
  • Codex warns loading hooks from both …/hooks.json and …/config.toml; prefer a single representation for this layer when both are present. Since Omnigent
    copies both, users with hooks in config.toml see this warning on every
    native-Codex session.

Environment

  • Omnigent 0.6.0, self-hosted server, Linux (Ubuntu 24.04)
  • Codex CLI 0.145.0, logged in via subscription
  • 21 MCP servers configured (not the cause — plain codex exec starts in ~7.8s)
  • Hooks: 5 events declared in ~/.codex/hooks.json, 3 more in ~/.codex/config.toml

Happy to test a patch against this setup — the failure reproduces every time.

Metadata

Metadata

Assignees

Labels

BugSomething isn't workingP1-highPriority: major feature broken, no workaroundcomp:harnessesComponent: SDK harnesses (Claude, Cursor, etc.)triagedIssue has been triaged by the bot

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions