SessionStart additionalContext does not preserve multiline hook context in TUI
What issue are you seeing?
SessionStart.hookSpecificOutput.additionalContext preserves multiline text in the model/context path, but the Codex TUI does not preserve that line structure when rendering the completed hook output in history.
The completed hook history cell appears to render each HookOutputEntry as one TUI Line. As a result, explicit newline characters inside hook output do not become separate TUI rows, making structured hook context hard to inspect.
This affects multiline output for completed hook entries such as:
ContextWarningErrorStopFeedback
Concrete example from a local SessionStart working-memory hook:
SessionStart hook (completed)
hook context: ## Working Memory Recall
Source: Codex compaction
Scope: Durable workspace memory
### Feedback
- ...
The model receives the intended multiline content, but the visible TUI history does not render it as a readable multiline block.
What steps can reproduce the bug?
- Register a
SessionStarthook that returns multilineadditionalContext. - Example hook output:
``json``
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "## Working Memory Recall\n\nSource: Codex compaction\nScope: Durable workspace memory\n\n### Feedback\n- Example preference"
}
}
- Start Codex TUI with hooks enabled.
- Observe the completed hook output in the transcript/history area.
Minimal behavior to inspect:
- the model-side injection path preserves the raw multiline string
- the TUI-side completed hook notification path does not render that structure as separate rows
What is the expected behavior?
The TUI should preserve explicit newline structure in completed hook output. For example:
• SessionStart hook (completed)
hook context: ## Working Memory Recall
Source: Codex compaction
Scope: Durable workspace memory
### Feedback
- Example preference
At minimum:
- hook authors should not need to flatten multiline context purely for display
- the TUI-visible hook output should preserve the same line structure that is sent to the model
- hook parsing, protocol payloads, and model injection behavior should remain unchanged
Additional information
Related issue: #16486 reports the same TUI rendering issue for UserPromptSubmit.additionalContext. This issue tracks the same root cause as observed through SessionStart.additionalContext.
I tested a small TUI-only fix against current main:
8a94430bb273623be42b68f144f1ab1df343bb53
Patch direction:
- split completed hook
entry.texton\nincodex-rs/tui/src/history_cell/hook_cell.rs - keep the hook output prefix (
hook context:,warning:, etc.) on the first physical line only - indent non-empty continuation lines by four spaces
- preserve explicit blank lines
- leave hook parsing, protocol, and model injection behavior unchanged
Patch shape:
let prefix = hook_output_prefix(entry.kind);
for (index, text) in entry.text.split('\n').enumerate() {
if index == 0 {
lines.push(format!(" {prefix}{text}").into());
} else if text.is_empty() {
lines.push("".into());
} else {
lines.push(format!(" {text}").into());
}
}
Coverage added locally:
- unit test for multiline
Context, including an explicit blank line, asserting bothdisplay_lines()andraw_lines() - unit test for multiline
Warning, asserting the prefix stays on the first line and continuation lines are indented - existing chatwidget snapshot updated so a
SessionStartcontext containingsession context\nsecond linerenders on two rows
Local verification:
PATH=/opt/homebrew/opt/rustup/bin:$PATH just fix -p codex-tui
# exit 0
PATH=/opt/homebrew/opt/rustup/bin:$PATH CARGO_NET_GIT_FETCH_WITH_CLI=true just test -p codex-tui
# 2635 tests run: 2635 passed, 4 skipped
# bench-smoke completed with exit 0
PATH=/opt/homebrew/opt/rustup/bin:$PATH cargo insta pending-snapshots
# No pending snapshots.This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗