Codex CLI renders hook additionalContext as visible developer message
Summary
Codex CLI is rendering hook hookSpecificOutput.additionalContext as a visible developer message in the session transcript.
This appears to contradict the documented behavior used by the Hindsight Codex integration, which says auto-recall should be "invisible to the transcript, visible to Codex".
Environment
- Codex CLI version:
0.118.0 - Platform: Linux
- Hook integration: Hindsight Codex integration
Expected behavior
When a UserPromptSubmit hook returns:
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "..."
}
}
the additionalContext should be available to the model without being shown in the user-visible transcript.
Actual behavior
The additionalContext payload is inserted into the session as a visible developer message containing the full Hindsight memory block.
In the same setup, Codex also shows visible UI status like:
• Running Stop hook
The main bug here is the visible transcript rendering of additionalContext.
Reproduction
- Enable Codex hooks.
- Configure a
UserPromptSubmithook that returnshookSpecificOutput.additionalContext. - Use the Hindsight Codex integration, or any minimal hook that returns hidden context this way.
- Start a session and submit a prompt.
- Observe that the hook context is shown in the transcript as a visible
developermessage.
Relevant docs
Hindsight integration docs state that auto-recall is:
- "invisible to the transcript"
- "visible to Codex"
Reference:
Local evidence
The Hindsight hook returns additionalContext here:
output = {
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": context_message,
}
}
json.dump(output, sys.stdout)
And the resulting session log contains it as a visible developer message:
{"type":"response_item","payload":{"type":"message","role":"developer", ... "<hindsight_memories> ... </hindsight_memories>"}}
Notes
This may be:
- a Codex CLI regression in how hook
additionalContextis rendered, or - a mismatch between current Codex behavior and the documented hook contract relied on by integrations.
16 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Adding another concrete use case here.
I rely on
UserPromptSubmit.additionalContextto inject strong behavioral guidance on every turn. Behaviorally, this works well. The problem is almost entirely presentation:So from this side, the issue is not "hooks are too verbose". The issue is that Codex currently makes hook context too visible.
Ideal behavior would be one of:
additionalContextmodel-visible but transcript-invisibleUserPromptSubmit hook appliedhidden | collapsed | fullIf Codex hid or collapsed this automatically, the current hook architecture would be fine. The operational behavior is already good; the transcript rendering is the thing pushing users toward worse designs.
I dug into this locally and I think the root cause is pretty narrow.
hookSpecificOutput.additionalContextis correctly being turned into model-visible developer context, but it currently goes throughrecord_conversation_itemsincodex-rs/core/src/hook_runtime.rs. That path emits/persists a raw transcript item, which is why integrations like Hindsight end up showing the full memory block as a visibledevelopermessage.The behavior I think would solve this cleanly is:
additionalContextmodel-visible.Where:
hiddeninjects the context for the model but renders no hook output entry.collapsedinjects the context and renders a small marker likeadditional context applied.fullpreserves the current visible/full-context behavior for users who want that.I have a local patch along those lines with regression coverage. It changes the additional-context recording path to use in-memory history instead of
record_conversation_items, adds the per-hook visibility enum/config, updates generated config/app-server schemas, and covers the hidden/collapsed/full behavior in hook tests.Validation run locally:
Happy to open a PR if this approach matches the intended hook contract.
Thanks for the detailed discussion here — I dug into the Codex path and I believe there are two separable concerns:
The narrow bug appears to be in
codex-rs/core/src/hook_runtime.rs, wherehookSpecificOutput.additionalContextis converted into developer-roleResponseItems and then passed throughrecord_conversation_items.That helper currently:
For normal conversation items, that is correct. For hook
additionalContext, this seems too broad.If the contract is “model-visible, transcript-invisible,” model visibility is already satisfied by adding these items to in-memory history. The persistence/event emission side effects are what make this content appear in transcript/session-log surfaces.
Minimal fix
Update
record_additional_contextsto record only into in-memory history:instead of:
(Important detail: parameter order differs between these APIs.)
This preserves the important behavior:
additionalContextRawResponseItememission for app-server/v2 transcript renderingRegression test shape
A good regression test would assert:
Optional follow-up (UX/config)
A configurable rendering mode could still be useful, e.g.:
Where:
hidden: inject for model, render nothingcollapsed: inject for model, render a small marker (e.g. “additional context applied”)full: preserve current fully visible behaviorBut I’d treat this as a follow-up UX/config layer. The core issue is that
additionalContextcurrently flows through the same recording path as transcript-visible conversation items.Adding one more concrete large-context use case and validation point.
Environment:
codex-cli 0.128.0Darwin 25.4.0 arm64 armGhostty 1.3.1UserPromptSubmithooksUse case:
A project-level agent harness needs to inject a large session-start context block into the model. The payload is not ordinary chat content; it is source/context material such as:
The desired UX is:
Current behavior:
UserPromptSubmit.hookSpecificOutput.additionalContextis both model-visible and transcript-visible. In a real harness test, a new-session hook returned a compact report plus a source-pack-like context payload. Codex accepted the hook output, but the entire payload was rendered into the visible CLI transcript as hook/developer context.Observed result from a sanitized local test:
If the harness suppresses the large
additionalContextto avoid flooding the CLI, the model no longer receives the full session-start context. That creates a hard tradeoff:This is exactly the kind of case where a model-visible but hidden/collapsed hook context channel is needed.
Minimal reproduction shape:
Expected behavior:
Codex should provide a way for hooks to send context that is model-visible without rendering the entire payload into the visible transcript.
Potential API/implementation directions:
additionalContext, for example:Semantics:
hidden: model-visible, no transcript renderingcollapsed: model-visible, transcript shows only a small marker/summaryfull: current behavior, render the whole payloadRecord hook-provided
additionalContextinto the in-memory model history used for the next request, but do not persist/emit it through the same transcript/session-log/raw-response-item path as normal visible conversation items unless visibility is explicitlyfull.This would preserve the important behavior:
additionalContextfullThis issue is not about hiding tool execution from users. A compact marker or audit line is still useful. The problem is that the full model-context payload is currently forced into the human-facing transcript.
Indexed this hook ticket in the umbrella tracker: #21753
Goal: collect the scattered Codex hook requests and bugs into one parity matrix for Full Claude Code Hook Parity (29+), while preserving this issue as the detailed thread for its specific behavior.
I prepared a small patch for this in a fork because direct PR creation against
openai/codexis currently blocked for my account (CreatePullRequestpermission denied via GraphQL; REST returned 404).Patch/branch:
Approach:
additionalContextmodel-visible by recording it into in-memory conversation historyHookCompletedcontext entry text with a compact byte-count marker, so the full payload no longer floods the TUI/transcriptValidated locally with:
PATH="$HOME/.cargo/bin:$PATH" cargo test -p codex-core hook_runtime::testsPATH="$HOME/.cargo/bin:$PATH" cargo test -p codex-hooksThis is intended to address the core issue here while also aligning with #21696 / #20766.
The dangerous part is not only that ^GdditionalContext becomes visible, it is that the receipt boundary collapses. Hidden runtime context and visible transcript context serve different jobs, and once they merge, integrations can no longer rely on either privacy or clean postmortems. I would want Codex to keep a separate provenance tag for hook-provided hidden context so the model can consume it while the operator can still audit that it existed without seeing it rendered as a normal developer message.
mark
I can reproduce this same class of issue with a Superpowers
SessionStarthook setup.Superpowers returns
hookSpecificOutput.additionalContextso the model receives startup instructions. Today Codex surfaces that context through two separate visible paths:HookCompletedentries asHookOutputEntryKind::Context, which the TUI renders ashook context: ...record_additional_contextscurrently usesrecord_conversation_items, making the context transcript-visibleI prepared a focused patch here:
The approach is intentionally narrower than a fold/collapsed UI feature:
additionalContextmodel-visiblesuppressOutput: trueforSessionStart,SubagentStart, andUserPromptSubmitcontext hook entriesPreToolUse/PostToolUsesuppressOutputbehavior unchanged for nowA direct PR against
openai/codexis currently blocked for my account withCreatePullRequestpermission denied, so I pushed the patch to a fork and linked the compare above.Maintainer questions:
additionalContextmodel-visible but transcript/raw-response-hidden?suppressOutputfirst forSessionStart/SubagentStart/UserPromptSubmit, while leaving the currently unsupported tool-hook cases alone?HookCompletedcontext entry stay fully visible unlesssuppressOutput: true, or would maintainers prefer hidden-by-default for alladditionalContext?Thanks for documenting this. I took a quick look and wanted to share a possible direction, in case it helps.
It looks like the core issue may be that hook
additionalContextis currently flowing through the same path that records conversation items and emits raw response items for the visible transcript.One possible fix direction could be to keep those hook-provided developer messages available to the model, but avoid emitting them through the raw response item path that the visible transcript consumes.
Concretely, that would mean:
additionalContextin the model-visible conversation historyI tried a small local patch in that direction and the focused hook runtime tests passed locally:
cargo fmt --checkcargo test -p codex-core hook_runtime::tests --libPatch reference, only as a discussion aid:
https://github.com/openai/codex/compare/main...zycaskevin:fix/hook-additional-context-transcript-visibility
I hit the same blocker while porting lore's Claude Code integration to Codex.
Context: lore injects deterministic coding conventions through hook
hookSpecificOutput.additionalContext. On Claude Code this is model-visible but not displayed in theoperator transcript, which is essential: the injected payload can be full pattern files/pages of
project conventions.
I built a local Codex plugin/adapter and verified:
SessionStart,PreToolUse,UserPromptSubmit, andPostToolUsefixtures are acceptedadditionalContextreaches the modelBut the UX is not shippable because Codex renders the injected context visibly in the terminal. This means pages of hook-injected convention text are interleaved with normal agent output, making it hard for the operator to distinguish injected context from conversation.
For this integration, summarizing/truncating is not a viable workaround. The product requirement is full-fidelity, deterministic, model-visible context that is hidden from the operator transcript, as Claude Code provides.
Public branch with the experimental integration and blocker docs: https://github.com/attila/lore/tree/feat/codex-plugin
The requested capability is either:
hookSpecificOutput.additionalContextin the visible TUI transcript, while still making it model-visible; orWithout that, full-body hook context integrations are effectively blocked on Codex CLI.
Possible strong relation to #28736.
That bug reproduces
SessionStarthooks forsource=compactfiring on later turns when no new compaction actually took place, due to duplicate compact events being drained later. If a hook injectsadditionalContext, that can cause extra unexpected developer-message/context pollution on a later turn, which looks directly relevant to visible reinjection symptom here.I prepared a focused patch against current
mainat8268cbfb0e5f39cb4efff928264fe8f29ddacafb.The root cause is that
record_additional_contextscurrently callsrecord_conversation_items, which does three things at once: updates model history, persists the item to the rollout, and emitsRawResponseItem. Hook context needs only the first behavior.This patch:
additionalContextin the in-memory history sent to the model;suppressOutput: truefor theUserPromptSubmitcontext entry, while keeping stop/block/error feedback visible;suppressOutput;With the patch, a hook can request completely silent context injection with:
<details>
<summary>Patch</summary>
</details>
Red/green validation:
suppress_output_hides_context_entryfailed because a fullContextentry was emitted.record_additional_contexts_stays_out_of_transcript_eventsfailed becauseevents.try_recv()received a raw response item.just test -p codex-hooks: 121 passed.just test -p codex-core hook_runtime::tests: 6 passed.just fmt: passed.git diff --check: passed.I did not run the complete workspace test suite; the results above are the focused affected-crate suites. I have not opened an upstream PR because
docs/contributing.mdsays external PRs are invitation-only. If a maintainer confirms this direction, I can package the same patch as an invited PR.This seems to be fixed for me in v0.143.0. The output still renders to the user but using
… +1044 lines (ctrl + t to view transcript)to hide long output.For anyone tracking this: the two upstream attempts at a fix (the
UserInstructionsstack #30138–#30141 and the async hook stack #31885–#31887) were both closed unmerged in the week of 2026-07-06, and the community patch above is still awaiting review.I've asked for maintainer direction on the intended design over in #21696, since that's where the design options are laid out. This bug and that feature request resolve the same underlying coupling of model visibility and TUI rendering.