Codex Desktop cross-thread delegation can silently wake a dormant thread and execute with stale context
What version of the Codex App are you using (From “About Codex” dialog)?
26.707.3748.0
What subscription do you have?
ChatGPT Pro
What platform is your computer?
Windows 11 x64, version 10.0.26200
What issue are you seeing?
A cross-thread sibling message can silently resume an old, unarchived Codex Desktop thread and start a new turn using that thread's historical context.
The sender intended to contact a currently live sibling/agent. Instead, the cross-thread routing path targeted a durable thread that had been idle for more than seven days. The target received the input as a codex_delegation envelope containing a source_thread_id and prompt, then immediately started executing the request under the old thread's prior conversation and instructions.
This is dangerous for agent orchestration because a durable thread ID is currently usable as if it were a live-agent handle. The sending tool does not expose or require a freshness/liveness/superseded-state precondition, and the target thread does not require confirmation before a dormant conversation is resumed in the background.
Sanitized evidence from one incident:
- Dormant target thread created:
2026-07-04T18:11:09Z - Delegation-triggered turn started:
2026-07-12T08:08:37Z - Dormant age at wake:
655,048 seconds(about 7.58 days) - The target was an existing local Desktop thread and was not archived.
- New input type:
codex_delegation - Envelope fields observed:
source_thread_idandinput - The target automatically started a turn and launched read-only diagnostic probes.
- The turn continued for
3,108 secondsuntil an explicit/stopwas sent. - The target's previous conversation was unrelated to the new incident, so its old instructions and assumptions were still present in context.
The cross-thread mechanism used was the Codex Desktop thread-management tool send_message_to_thread (or the equivalent internal cross-thread delivery path). Locally, messages sent through this tool are persisted in the target transcript as codex_delegation inputs. This behavior is also described in adjacent issue #29886.
The likely boundary failure is:
send_message_to_threadaddresses a durable thread ID.- An idle, unarchived historical thread remains resumable.
- Delivery starts a turn immediately instead of first checking whether the target is the currently live sibling represented by the sender's stale handle.
- There is no compare-and-set precondition such as expected target status, expected last-updated timestamp, current agent lease, or explicit permission to resume a dormant thread.
- The target therefore executes with its full historical context.
This report contains no raw prompts, repository names, local paths, user identifiers, business data, secrets, or full thread IDs.
What steps can reproduce the bug?
- Create thread B in Codex Desktop and give it a distinct task/context.
- Finish the turn but leave thread B unarchived and idle.
- Wait long enough that thread B is operationally obsolete or superseded.
- In a different thread A, use the cross-thread sibling-routing path /
send_message_to_threadwith B's stored thread ID. - Send a new delegation intended for a currently live sibling.
- Inspect B with
read_thread.
Observed result:
- B receives a new
codex_delegation. - B automatically starts a turn.
- B processes the new instruction using its old conversation context.
- No confirmation warns that B is dormant, stale, or superseded.
A safer repro should use a benign prompt that performs no tools or external writes.
What is the expected behavior?
Cross-thread messaging should distinguish a live agent from a durable historical thread.
At minimum:
- Default to fail closed when the target is archived, superseded,
notLoaded, or older than a configurable freshness threshold. - For an idle/dormant target, return target metadata and require an explicit
resumeDormant: trueor user confirmation before starting a turn. - Support optimistic concurrency fields such as
expectedUpdatedAt,expectedStatus, or a live-agent lease/epoch so stale handles cannot wake a different lifecycle generation. - Add a real
superseded/acceptExternalMessages=falsestate distinct from archive. - Prefer live subagent/agent handles for sibling communication; do not treat a durable thread ID as proof of agent liveness.
- Allow a queued/inbox-only delivery mode that does not execute until the target/user accepts it.
- Show an auditable source/target notification before background execution.
- Archived threads must remain non-resumable through background delegation.
Additional information
Immediate workaround tested:
- Stop the unintentionally awakened turn.
- Archive the obsolete target while it is not the currently viewed/active thread.
- Verify that it no longer appears in the normal thread list.
- For future sibling coordination, use only live-agent handles from the current orchestration tree, or perform a read-only preflight and verify exact thread ID, status, freshness, host, and ownership before sending.
Archiving is only a partial workaround. Existing archive/resume issues (#25713 and #25769) show that archive lifecycle handling has its own edge cases, although the app-server correctly rejects resume attempts for archived sessions.
No exact duplicate was found. Related but distinct:
- #14923 — general opt-in cross-thread orchestration and permissions
- #29886 —
send_message_to_threadcan report an error despite delivery - #30499 — request for non-interrupt queued cross-thread delivery
- #30626 — cross-host child placement and completion handoff
- #25713 / #25769 — archived-thread resume handling
Suggested product-level regression tests:
- A stale
expectedUpdatedAtcannot start a target turn. - An archived or superseded target cannot receive an executable background delegation.
- An idle target older than the freshness threshold requires explicit opt-in.
- A live-agent lease mismatch rejects delivery without starting a turn.
- Inbox-only delivery never executes automatically.
- The sender receives a receipt containing target status, age, host, and whether a turn was started.