Hooks: Stop hook systemMessage is forced to `warning:` — no info/notice severity for end-of-turn notes

Open 💬 3 comments Opened Jun 24, 2026 by kfastino

What variant of Codex are you using?

CLI

What version of Codex are you using?

codex-cli 0.141.0

What happened?

A Stop hook's systemMessage is always rendered in the TUI with the warning: prefix, even when the message is purely informational. There is no info/notice severity available to an end-of-turn hook, so passive status lines (cost/usage summaries, routing info, "all checks passed", etc.) are surfaced as warnings.

Example — this Stop hook output:

{ "systemMessage": "Auto-routing savings this session: ~$0.94 (vs <reference-model>)" }

renders as:

• Stop hook (completed)
  warning: Auto-routing savings this session: ~$0.94 (vs <reference-model>)

Why this isn't fixable from the hook side

In codex-rs/hooks/src/events/stop.rs, parse_completed maps systemMessage unconditionally to HookOutputEntryKind::Warning:

if let Some(system_message) = parsed.universal.system_message {
    entries.push(HookOutputEntry {
        kind: HookOutputEntryKind::Warning,   // forced
        text: system_message,
    });
}

For a Stop hook the only reachable entry kinds are Warning (from systemMessage), Stop (from continue:false + stopReason), Feedback (from decision:"block"), and Error. The neutral HookOutputEntryKind::HookContext (hook context: prefix, added in #17266) is never produced for Stop, and hookSpecificOutput.additionalContext isn't an accepted Stop field (deny_unknown_fields).

The available alternatives don't work as passive notes:

  • decision:"block" + reason → renders as feedback: but force-continues the turn (injects a new user prompt).
  • exit code 2 + stderr → also treated as a turn continuation.
  • plain-text stdout → invalid for Stop.

So systemMessagewarning: is the only passive, user-visible channel for an end-of-turn hook, and it's hardwired to the warning label.

What I expected

A way for an end-of-turn (and ideally any) hook to surface a non-warning informational note. Options that would solve it:

  1. Add an info/notice entry kind (e.g. rendered as note: / info:) selectable from hook output — for example an optional severity on systemMessage, or a dedicated field.
  2. Allow Stop hooks to emit a neutral hook context:-style entry that is shown to the user (not injected into the model turn).

Why it matters

Stop hooks are a natural place for end-of-turn informational summaries (usage/cost, routing decisions, lint/test status). Today every one of these reads as a warning, which trains users to ignore the prefix and makes legitimately useful notes feel alarming.

Related (but distinct)

  • #25403 (quiet mode) and #15497 / #19383 — about suppressing hook output, not relabeling an info note.
  • #21753 (Claude hook parity) and #19385 (additionalContext in PreToolUse) — adjacent, but neither requests an info-severity user-visible entry for Stop.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗