Hooks: Stop hook systemMessage is forced to `warning:` — no info/notice severity for end-of-turn notes
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 asfeedback: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 systemMessage → warning: 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:
- Add an info/notice entry kind (e.g. rendered as
note:/info:) selectable from hook output — for example an optional severity onsystemMessage, or a dedicated field. - Allow
Stophooks to emit a neutralhook 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 (
additionalContextinPreToolUse) — adjacent, but neither requests an info-severity user-visible entry forStop.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗