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)
Nobody has claimed this yet.
- 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:
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_keyformat)codex-rs/config/src/hook_config.rs(HookStateToml, thetrusted_hashfield)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
SessionStartcommand hook inconfig.tomldoes not run incodex execwithout 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_hashderivation, thehook_keyformat, 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 execrun (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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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