Subagents inherit Codex App task controls and can create independent user-owned threads
What version of the Codex App are you using (From “About Codex” dialog)?
Codex App <code>26.810.41047</code>, build <code>6570</code>. The associated rollout metadata reports Codex <code>0.148.0-alpha.9</code>
The source checkout used for the analysis exactly matched upstream <code>main</code> at <code>2ca575026cef066a58c70f6bdee4feafa6e63d3a</code>
What subscription do you have?
ChatGPT paid plan. The exact tier is omitted from this public report because the failure is a local capability-boundary issue and does not appear plan-dependent
What platform is your computer?
<code>Darwin 25.5.0 arm64 arm</code>, macOS <code>26.5.1</code> build <code>25F80</code>
What issue are you seeing?
A real multi-agent child, after a third-party OMO/LazyCodex <code>SubagentStop</code> continuation hook incorrectly promoted the completed worker into the root plan orchestrator, used inherited Codex App dynamic tools to create two independent, user-owned Desktop tasks
The user had requested ordinary plan execution through subagents, not separate Desktop tasks. The OMO hook explains why the child attempted further delegation; it does not supply explicit user authorization to create user-owned tasks
The source conversation was a normal depth-1 <code>SessionSource::SubAgent(ThreadSpawn)</code> child with an incoming spawn edge and a parent thread. Its persisted <code>SessionMeta.dynamic_tools</code> nevertheless contained the complete <code>codex_app</code> namespace, including:
- <code>create_thread</code>
- <code>fork_thread</code>
- <code>handoff_thread</code>
- <code>send_message_to_thread</code>
- <code>set_thread_archived</code>
- <code>set_thread_pinned</code>
- <code>set_thread_title</code>
- the thread listing, reading, waiting, navigation, automation, and project functions
The tool description for <code>create_thread</code> correctly says: <code>Create a separate task only when the user explicitly asks for a new task.</code> However, that rule is only natural-language guidance. There is no hard runtime capability check preventing a subagent from calling it
The immediate orchestration trigger was OMO 4.19.4's <code>start-work-continuation</code> hook. On the worker's <code>SubagentStop</code>, the hook matched the parent/root session id stored in Boulder state, injected the remaining root plan into the completed child, and instructed it to delegate the next checkbox through native multi-agent tools. This is a separate OMO identity/scope bug
During the incident, the child entered Code Mode <code>exec</code> and searched for agent/collaboration capabilities. Native multi-agent functions were not exposed through that Code Mode catalog, but the inherited <code>codex_app</code> namespace was. The child discovered <code>codex_app__create_thread</code>, called it twice in parallel despite the tool's explicit-user-request rule, and both calls returned new thread IDs
The upstream issue is therefore a hard capability-boundary failure, not a claim that Codex independently invented the desire to delegate. A third-party hook and a model/tool-selection error triggered the calls; Codex still exposed and accepted root task-management operations from a known non-root caller
The resulting state rows were not subagents:
- <code>source = vscode</code>
- <code>thread_source = user</code>
- <code>forked_from_id = null</code>
- no <code>agent_path</code>, <code>agent_role</code>, or <code>agent_nickname</code>
- no incoming spawn edge
They appeared as normal independent tasks in the Desktop sidebar, continued under separate lifecycles, used independent permission profiles, and spawned their own descendants. This looked like subagents had escaped their parent, but the metadata shows that the child had actually created new root tasks
One generated task used an <code>on-request</code> restricted profile while the other used <code>never</code> with the sandbox disabled. This also explains why permission requests appeared independently and repeatedly instead of following the original subagent lifecycle
Later cross-task messages displayed <code>Sent by ChatGPT from another task</code>. That marker itself is behaving correctly: the installed App renders it for messages carrying <code>codexDelegation</code> and links back to the source thread. The bug is that a non-root child could create and coordinate user-owned tasks in the first place
Full session IDs and prompts are redacted from this public report. I can provide the local metadata through a private channel if useful; no raw prompts, credentials, command bodies, or user content are required to reproduce the capability problem
What steps can reproduce the bug?
OMO is not required for reproduction. It supplied the concrete trigger in this incident, but the upstream defect exists whenever any prompt, hook, orchestration error, or adversarial input causes a non-root session to attempt one of these user-owned task operations
A source-level and runtime reproduction is:
- Start a Codex App project task with multi-agent support and the normal <code>codex_app</code> dynamic tool catalog
- Spawn a full-history subagent
- Inspect the child session metadata or Code Mode <code>ALL_TOOLS</code>
- Observe that the child still has <code>codex_app__create_thread</code> and the other user-owned task-management functions
- From the child, invoke <code>codex_app__create_thread</code> with a valid project target
- Observe that the returned thread is a new <code>source = vscode</code>, <code>thread_source = user</code> root task with no spawn-edge parent and its own permission/lifecycle state
The relevant source path is deterministic:
- A fork loads the parent rollout, including session metadata, into the child history:
- When no explicit dynamic tool list is supplied, session startup restores dynamic tools from the inherited conversation history without consulting <code>SessionSource</code>:
- Tool planning registers every dynamic tool unconditionally even though it already has the turn context:
- Code Mode intentionally makes deferred or hidden dynamic tools discoverable through <code>ALL_TOOLS</code> and callable:
This means <code>deferLoading</code> changes presentation, not authorization
Codex already provides <code>SessionSource::is_non_root_agent()</code>, which could support the missing restriction:
There is also precedent for hard source-aware capability enforcement in the App Server: direct input is explicitly rejected for multi-agent v2 child sources
What is the expected behavior?
A non-root agent should not be able to create, fork, hand off to, mutate, archive, or directly message user-owned Codex App tasks merely because those dynamic tools were persisted in the root conversation and inherited through a history fork
This must be enforced even when a third-party hook incorrectly broadens the child's scope, the model selects the wrong tool, or an adversarial prompt attempts the operation. Those circumstances are exactly why a host-side authorization boundary is needed
Subtasks should remain on the multi-agent control plane. A separate user-owned task should be creatable only from a root or user turn with explicit user intent
I suggest enforcing this in both layers:
- Filter or reject user-owned task mutation tools for <code>SessionSource::SubAgent</code> and other internal non-root sessions
- Apply the policy to ordinary tool specs, tool search, Code Mode <code>ALL_TOOLS</code>, and direct or forged dynamic calls
- Add defense in depth in the App dynamic-tool handler so <code>create_thread</code>, <code>fork_thread</code>, <code>handoff_thread</code>, <code>send_message_to_thread</code>, and <code>set_thread_*</code> reject non-root callers unless a host-created explicit-user-intent capability is present
- Keep safe read-only App utilities available separately where intended instead of dropping the whole namespace
- Keep legitimate root-to-task delegation and the <code>Sent by ChatGPT from another task</code> marker working
A regression test should start a root App thread with dynamic tools, fork both v1 and v2 children using full-history and last-N inheritance, and assert that forbidden task-management functions are absent or rejected across every exposure path. It should also verify that an explicitly user-authorized root call still works
Additional information
A follow-up transcript audit established the complete causal chain:
- The scoped LazyCodex worker completed its assigned task
- OMO's <code>SubagentStop</code> continuation hook incorrectly injected the root plan's next checkbox into that child
- The hook instructed native subagent delegation, not creation of separate Desktop tasks
- The child searched Code Mode <code>ALL_TOOLS</code>, found inherited <code>codex_app</code> task controls, and called <code>create_thread</code>
- Codex accepted both calls and created user-owned root tasks with independent permission and lifecycle state
OMO's documented isolated Codex CLI reviewer was not invoked, and its Codex App team-mode transport was not activated. The accidental reviewer was one of the two Desktop tasks created by the child
This narrows responsibility without removing the upstream defect: OMO needs to stop resuming completed children as root orchestrators, while Codex must reject root-only task operations from non-root sessions regardless of what prompted the call
The incident occurred while model traffic was routed through Codex Pooler, so I audited its recently completed Agents v2 compatibility work before attributing the App capability failure to Codex
Codex Pooler only preserves canonical encrypted <code>agent_message</code> handoffs between agent paths. Its public implementation validates the <code>NEW_TASK</code> and <code>MESSAGE</code> envelopes here:
That gateway behavior cannot invoke a local App dynamic tool, insert a <code>source = vscode</code> user thread, create a sidebar conversation, select a local permission profile, or attach <code>codexDelegation</code> metadata. The source and target state records rule out the proxy handoff path, and the missing capability boundary is present in current upstream Codex source independently of Codex Pooler
Both accidentally created tasks eventually emitted explicit <code>task_complete</code>. This is not a report about permanently live threads. It is about unauthorized root-task creation, independent permissions, and a lifecycle/resource multiplier from a non-root agent
I searched open and closed issues and open pull requests for <code>codex_app</code>, <code>create_thread</code>, inherited dynamic tools, user-owned threads, and the delegation marker and did not find an existing report
5 Comments
Could you assign this to me
This is a useful session-graph/control case for
codex-rescue. Rescue does not fix thecodex_appauthorization boundary; the test is whether it distinguishes actual subagent descendants from the independently created user-owned root tasks and avoids folding or replaying those roots as child recovery state.If you still have the affected local session graph, could you run:
A healthy/unknown result can be correct here because both accidental root tasks eventually completed. Please don’t salvage solely to test the capability bug; I’m interested in discovery/classification and whether Rescue stays conservative around the parent/child/root boundaries.
Sanitized output + versions/exit codes only—no raw rollouts/SQLite, prompts, task IDs, credentials, or private paths. Repo: https://github.com/shleder/codex-rescue
Confirmed: this boundary now caused a second real-world incident on Windows Codex Desktop / rollout CLI
0.149.0-alpha.4.1.This incident did not require a child to create a new task. A normal depth-1
luna_workerchild already had the native parent-result channel, but it could also invokecodex_app__send_message_to_thread.Sanitized causal chain from the persisted source and target transcripts:
/rootthrough the App thread API and receivedInvalid conversation root.FINAL_ANSWER, it reused an unrelated durable thread ID available in inherited history.send_message_to_threadfive times against that unrelated old task.<codex_delegation>and displayed as “Sent by ChatGPT from another task”.This confirms the capability problem is not limited to
create_thread: a non-root child can also mutate and execute an unrelated user-owned task throughsend_message_to_thread. It also connects the permission boundary here with the stale-thread wake behavior in #32547 and active-turn interruption in #34933.No raw prompts, business content, paths, or full thread IDs are included. The source metadata identifies the caller as
SessionSource::SubAgent(ThreadSpawn), depth 1, roleluna_worker.Expected host-side behavior:
send_message_to_thread,create_thread,handoff_thread, and other user-task mutation tools for non-root callers unless backed by an explicit user-intent capability.To add an operator-level perspective on containing the
luna_worker/send_message_to_threadescalation pattern described by @UriPomer:Until host-side task authorization is patched upstream, relying solely on agent self-restraint is insufficient because spawned child processes inherit ambient local sockets and storage descriptors.
We addressed this capability leak at the OS perimeter level in Vetto using zero-daemon Landlock/Seatbelt boundaries:
$AGENT/app_server.sock,*.sock,*.ipc) and local SQLite state catalogs (state_*.sqlite).9222,9229,5678) and raw socket multiplexers (socat,ncat) trigger immediate fail-closed classification.For anyone needing immediate protection against subagent privilege escalation while running long-running Codex sessions, wrapping the invocation with
vetto --agent codex --profile strict -- codex exec ...enforces these kernel boundaries pre-execution without requiring background daemons.Additional reproduction: nested delegation without an active custom hook
I can reproduce the same capability-boundary problem on Windows with Codex App
26.820.7780.0.Observed setup
create_threadpath from a non-root task instead of remaining a native multi-agent child.<codex_delegation>wrapper identifying B as the source.create_thread/send_message_to_thread.In this run, the user-level custom hook configuration had already been reduced to an empty hook set before C was created. The parent conversation itself had started earlier, so stale inherited context cannot be ruled out, but the host still accepted root-task operations from a non-root delegation path.
Expected behavior
A non-root agent should not be able to create, fork, hand off, mutate, or directly message user-owned Codex App tasks. Native subagent results should return only through the direct parent multi-agent channel.
This appears to be the same root-task capability-boundary issue reported here, with an additional Windows/current-build reproduction. No project source, credentials, prompts, or private thread IDs are required to reproduce it.