Skip to content

Subagents inherit Codex App task controls and can create independent user-owned threads #38687

Description

@masterkain

What version of the Codex App are you using (From “About Codex” dialog)?

Codex App 26.810.41047, build 6570. The associated rollout metadata reports Codex 0.148.0-alpha.9

The source checkout used for the analysis exactly matched upstream main at 2ca575026cef066a58c70f6bdee4feafa6e63d3a

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?

Darwin 25.5.0 arm64 arm, macOS 26.5.1 build 25F80

What issue are you seeing?

A real multi-agent child, after a third-party OMO/LazyCodex SubagentStop 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 SessionSource::SubAgent(ThreadSpawn) child with an incoming spawn edge and a parent thread. Its persisted SessionMeta.dynamic_tools nevertheless contained the complete codex_app namespace, including:

  • create_thread
  • fork_thread
  • handoff_thread
  • send_message_to_thread
  • set_thread_archived
  • set_thread_pinned
  • set_thread_title
  • the thread listing, reading, waiting, navigation, automation, and project functions

The tool description for create_thread correctly says: Create a separate task only when the user explicitly asks for a new task. 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 start-work-continuation hook. On the worker's SubagentStop, 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 exec and searched for agent/collaboration capabilities. Native multi-agent functions were not exposed through that Code Mode catalog, but the inherited codex_app namespace was. The child discovered codex_app__create_thread, 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:

  • source = vscode
  • thread_source = user
  • forked_from_id = null
  • no agent_path, agent_role, or agent_nickname
  • 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 on-request restricted profile while the other used never 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 Sent by ChatGPT from another task. That marker itself is behaving correctly: the installed App renders it for messages carrying codexDelegation 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:

  1. Start a Codex App project task with multi-agent support and the normal codex_app dynamic tool catalog
  2. Spawn a full-history subagent
  3. Inspect the child session metadata or Code Mode ALL_TOOLS
  4. Observe that the child still has codex_app__create_thread and the other user-owned task-management functions
  5. From the child, invoke codex_app__create_thread with a valid project target
  6. Observe that the returned thread is a new source = vscode, thread_source = user root task with no spawn-edge parent and its own permission/lifecycle state

The relevant source path is deterministic:

  1. A fork loads the parent rollout, including session metadata, into the child history:
  2. When no explicit dynamic tool list is supplied, session startup restores dynamic tools from the inherited conversation history without consulting SessionSource:
  3. Tool planning registers every dynamic tool unconditionally even though it already has the turn context:
  4. Code Mode intentionally makes deferred or hidden dynamic tools discoverable through ALL_TOOLS and callable:

This means deferLoading changes presentation, not authorization

Codex already provides SessionSource::is_non_root_agent(), 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:

  1. Filter or reject user-owned task mutation tools for SessionSource::SubAgent and other internal non-root sessions
  2. Apply the policy to ordinary tool specs, tool search, Code Mode ALL_TOOLS, and direct or forged dynamic calls
  3. Add defense in depth in the App dynamic-tool handler so create_thread, fork_thread, handoff_thread, send_message_to_thread, and set_thread_* reject non-root callers unless a host-created explicit-user-intent capability is present
  4. Keep safe read-only App utilities available separately where intended instead of dropping the whole namespace
  5. Keep legitimate root-to-task delegation and the Sent by ChatGPT from another task 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:

  1. The scoped LazyCodex worker completed its assigned task
  2. OMO's SubagentStop continuation hook incorrectly injected the root plan's next checkbox into that child
  3. The hook instructed native subagent delegation, not creation of separate Desktop tasks
  4. The child searched Code Mode ALL_TOOLS, found inherited codex_app task controls, and called create_thread
  5. 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 agent_message handoffs between agent paths. Its public implementation validates the NEW_TASK and MESSAGE envelopes here:

That gateway behavior cannot invoke a local App dynamic tool, insert a source = vscode user thread, create a sidebar conversation, select a local permission profile, or attach codexDelegation 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 task_complete. 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 codex_app, create_thread, inherited dynamic tools, user-owned threads, and the delegation marker and did not find an existing report

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appbugSomething isn't workingsubagentIssues involving subagents or multi-agent features

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions