openai / openai/codex

SessionStart hook in config.toml is silently skipped in codex exec unless --dangerously-bypass-hook-trust is set, with no diagnostic and no documented trust path (0.153.4)

Open
#46,210 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug CLI config exec hooks
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

A SessionStart hook declared in config.toml does nothing in a headless codex exec run and prints no reason at all, so automation cannot tell a hook that was held back for trust from one that is broken or misconfigured. A silent no-op with zero diagnostic is a support cost and a trust-model footgun: the hook trust gate is a reasonable safety design, but with no signal on the skip path it reads as "codex hooks are broken," and the only documented way to make the hook run is a flag whose own help text calls it dangerous.

Verified on codex 0.153.4 (macOS arm64, release rust-v0.153.4).

Expected

A SessionStart command hook declared in config.toml either runs when a session starts, or, if it is held back for trust reasons, codex emits a diagnostic saying so. docs/config.md documents how to declare a hook and how to establish the trust it needs.

Actual

In codex exec, a SessionStart command hook from user or project config.toml does not run and codex prints nothing about it. It runs only when --dangerously-bypass-hook-trust is passed. The trust state that would let it run without that flag is hooks.state.<key>.trusted_hash, which is written by the interactive TUI review flow. Its map key format and its hash derivation are not documented, and there is no non-interactive command to set it.

Smallest decisive repro

Two codex exec runs of the identical config. A dummy provider makes each run reach session start and then fail on the network at the same point, so the network failure is constant and the only variable is the trust flag. Without the flag the hook does not fire and nothing is printed about it; with the flag the same hook fires.

Script and one clean top-to-bottom run (codex 0.153.4)
#!/usr/bin/env bash
set -euo pipefail

WORK="$(mktemp -d)"
export CODEX_HOME="$WORK/codex-home"
WORKDIR="$WORK/wd"
MARKER="$WORK/hook-fired.marker"
mkdir -p "$CODEX_HOME" "$WORKDIR"

cat > "$CODEX_HOME/config.toml" <<EOF
model = "test-model"
model_provider = "dummy"

[model_providers.dummy]
name = "dummy"
base_url = "http://127.0.0.1:9/v1"
env_key = "DUMMY_API_KEY"
wire_api = "responses"

[[hooks.SessionStart]]
[[hooks.SessionStart.hooks]]
type = "command"
command = "printf fired > $MARKER"
EOF

echo "##### RUN 1: no flag #####"
rm -f "$MARKER"
DUMMY_API_KEY=x codex exec --skip-git-repo-check -C "$WORKDIR" "hi" </dev/null 2>&1 \
  | grep -vE 'Reconnecting|Reading additional' || true
test -f "$MARKER" && echo "MARKER PRESENT: [$(cat "$MARKER")]" || echo "MARKER ABSENT (hook did not run)"

echo "##### RUN 2: --dangerously-bypass-hook-trust #####"
rm -f "$MARKER"
DUMMY_API_KEY=x codex exec --dangerously-bypass-hook-trust --skip-git-repo-check -C "$WORKDIR" "hi" </dev/null 2>&1 \
  | grep -vE 'Reconnecting|Reading additional' || true
test -f "$MARKER" && echo "MARKER PRESENT: [$(cat "$MARKER")]" || echo "MARKER ABSENT"

Output (lines about the unreachable dummy endpoint and the stdin prompt read are filtered for readability):

##### RUN 1: no flag #####
OpenAI Codex v0.153.4
--------
workdir: /.../wd
model: test-model
provider: dummy
approval: never
sandbox: read-only
reasoning effort: none
reasoning summaries: none
session id: 01a0af7d-b630-7323-b764-9ef0a94f2918
--------
user
hi
warning: Model metadata for `test-model` not found. Defaulting to fallback metadata; this can degrade performance and cause issues.
MARKER ABSENT (hook did not run)
##### RUN 2: --dangerously-bypass-hook-trust #####
OpenAI Codex v0.153.4
--------
workdir: /.../wd
model: test-model
provider: dummy
approval: never
sandbox: read-only
reasoning effort: none
reasoning summaries: none
session id: 01a0af7e-5264-7a41-8789-a7fad566ffef
--------
user
hi
warning: `--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation.
warning: `--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation.
warning: Model metadata for `test-model` not found. Defaulting to fallback metadata; this can degrade performance and cause issues.
hook: SessionStart
hook: SessionStart Completed
MARKER PRESENT: [fired]

Run 1 writes no marker and prints no line about the hook. Run 2 prints hook: SessionStart and writes the marker. The config is identical; the only difference is the flag.

Mechanism

A hook handler is collected to run only when it is enabled and either trust is bypassed or the trust status is Managed/Trusted:

https://github.com/openai/codex/blob/3d2ee51ca2d5db578f328aa75e20aa22c0197c9a/codex-rs/hooks/src/engine/discovery.rs#L713-L720

For a user or project hook with no persisted trust, hook_trust_status returns Untrusted (its trusted_hash is None), so the handler is not collected. There is no else branch and no warning on that path. The codex exec entrypoint carries bypass_hook_trust through but emits nothing when a hook is dropped for trust; the "needs review before it can run" text exists only under codex-rs/tui/, so it never reaches a headless run.

trusted_hash is a sha256 over a normalized TOML identity of the hook (hook_hash), keyed by hook_key = "{key_source}:{event_label}:{group_index}:{handler_index}". The interactive TUI computes and persists it via config/batchWrite. Nothing in codex-rs/exec or codex-rs/cli writes it.

docs/config.md section "Lifecycle hooks" documents only allow_managed_hooks_only. A search of the docs/ tree at this release returns zero matches for each of trusted_hash, dangerously-bypass-hook-trust, and hooks.state.

Relevant paths (at rust-v0.153.4):

  • codex-rs/hooks/src/engine/discovery.rs (the trust gate, hook_hash, hook_trust_status)
  • codex-rs/hooks/src/lib.rs (hook_key format)
  • codex-rs/config/src/hook_config.rs (HookStateToml, the trusted_hash field)
  • codex-rs/exec/src/lib.rs (exec carries the flag, warns nothing on the drop path)
  • docs/config.md (section "Lifecycle hooks")
What was and was not checked

Checked, executed on a real 0.153.4 binary:

  • A SessionStart command hook in config.toml does not run in codex exec without the flag, and runs with it. The two-run control isolates the trust gate.
  • No stderr or warning about the skip in the no-flag run.

Checked, source at rust-v0.153.4:

  • The trust gate, the trusted_hash derivation, the hook_key format, and the absence of an exec-path warning.
  • The docs/ tree has no coverage of the trust surface.

Not checked:

  • The interactive TUI trust flow end to end (no model call completes against the dummy provider, so the TUI review path was not exercised).
  • Whether a hash trusted in a prior TUI session carries into a later codex exec run (expected from the code, not executed).
  • Whether the silent drop is intended as fail-closed behavior or is a UX gap. The ask below is framed either way.
Ask

Could codex exec emit a short diagnostic when an enabled hook is skipped for trust (naming the hook and how to trust it, as it already warns when the bypass flag is set), and could docs/config.md document trusted_hash and the --dangerously-bypass-hook-trust flag, including whether there is a supported non-interactive way to establish trust for automation?

Happy to follow up with a PR if that would help.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the trust gate and hook_key logic in codex-rs/hooks/src/engine/discovery.rs and codex-rs/hooks/src/lib.rs, then trace the skipped-hook path in codex-rs/exec/src/lib.rs. Review codex-rs/config/src/hook_config.rs and the Lifecycle hooks section of docs/config.md. Done means trust-skipped hooks produce a useful exec diagnostic and the supported trust path, trusted_hash, and bypass flag are documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli, documentation, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.