Guardian approval-review sub-session reused as user's main thread — judge base_instructions leak, main thread keeps replying {"outcome":"allow"}

Open 💬 0 comments Opened Jul 17, 2026 by fengyat

Bug: Guardian approval-review sub-session gets reused as the user's main conversation thread (judge prompt leaks into user turns)

Codex version: recent 0.144.x
Surface: Codex VS Code extension / Desktop
Provider: custom model_provider (OpenAI-compatible, wire_api = "responses")

Summary

When "Approve for me" (auto-review guardian) is enabled, Codex spawns a hidden
guardian approval-review sub-agent session whose base_instructions are the
fixed "You are judging one planned coding-agent action..." judge prompt.

A guardian sub-session ends up persisted and later resumed/displayed as the
user's own main conversation thread
. Because a session's base_instructions
are fixed at creation and never re-derived on resume (see
core/src/session/mod.rs: precedence is config.base_instructions
conversation_history session_meta.base_instructions → model default), the
judge prompt stays pinned as the system instruction for the user-facing thread.

Result: for every normal user request, the main model answers as an approval
judge and emits {"outcome":"allow"} (or a full
{"risk_level":...,"outcome":"allow","rationale":...}) instead of doing the
work. It gets worse after each auto-compaction — once history is replaced by
a summary, the pinned judge base_instructions dominate, and the thread emits
approval JSON to even trivial prompts (e.g. a user typing "1").

Evidence (from an affected session's rollout session_meta, redacted)

A single conversation id carries BOTH identities across its session_meta
entries in the same rollout file:

session id:      <UUID>
forked_from_id:  <UUID>            # forked from a parent thread
source:          cli               # <-- user-facing thread
thread_source:   user
base_instructions: "You are judging one planned coding-agent action. Assess ..."   # <-- guardian JUDGE prompt

# ...and elsewhere in the SAME file:
source:          {subagent: {other: "guardian"}}   # <-- guardian sub-agent identity
thread_source:   subagent

So the user-facing thread (source: cli, thread_source: user) has the
guardian judge prompt as its base_instructions, and the same file also
contains the guardian subagent identity — the two were merged into one thread.

Early turn_context entries use model = "codex-auto-review" (guardian); later
ones switch to the user's configured model via an injected <model_switch>
developer message — but base_instructions is never reset, so the judge prompt
persists.

Expected

A guardian approval-review sub-session must never become the user's main thread.
On resume, the user thread's base_instructions should be the normal agent
prompt for the current model, not a guardian judge prompt inherited from a
sub-agent session.

Impact

The user's main conversation becomes unusable: every turn returns
{"outcome":"allow"} instead of doing work. It is not self-healing within the
session (base_instructions are immutable), so the only recovery is starting a
new conversation, losing conversational context.

Repro (observed)

  1. Custom OpenAI-compatible provider, wire_api = "responses".
  2. Enable "Approve for me" (auto-review guardian).
  3. Use the session across actions and let auto-compaction trigger a few times.
  4. Main thread begins returning {"outcome":"allow"} to normal requests,

especially right after context-compacted events.

---

Related issues (same guardian-subagent / user-thread boundary area, different symptoms):

  • #32035 — guardian subagent threads exposed in the conversation list; main thread can't resume/fork
  • #33670 — guardian threads retain parent in rollouts but absent from thread_spawn_edges

This report differs from those: here the guardian judge base_instructions itself is inherited by the user-facing thread, so the main model keeps answering with approval JSON instead of doing the work.

View original on GitHub ↗