Android Remote replays one create_thread operation into seven thread/start requests
Bug report: Android Remote creates multiple tasks from one create_thread call
Summary
While controlling Codex on a Windows host from the ChatGPT Android Remote surface during a Realtime Voice session, a request to create one new projectless task produced seven separate top-level tasks.
This was not only a duplicated UI entry. The host persisted seven distinct threads with different thread IDs, turn IDs, rollout files, and projectless working directories.
Environment
- Host OS: Windows
- Codex desktop build:
26.721.11231.0 - Codex core/CLI version recorded in the rollout:
0.146.0-alpha.3.1 - Remote client reported by app-server:
codex_chatgpt_android_remote - Remote client version reported by app-server:
dev - Parent thread source: Realtime Voice
- New-task target: projectless
What happened
- From Android Remote, I asked an existing Realtime Voice thread to create one separate projectless task.
- The parent rollout contains only one
codex_app__create_threadtool call, with one call ID. - That tool call returned one thread ID.
- However, the Windows host received seven
thread/resumerequests for the same parent thread and the same active turn, all on the same app-server connection. - The same connection then issued seven relevant
thread/startrequests. - Seven distinct top-level threads were persisted. They had the same delegated input and the same source parent, but different thread IDs, turn IDs, rollout files, and numbered projectless working directories.
Expected behavior
One codex_app__create_thread call should create exactly one new task. Resuming or reopening the active parent thread must not re-execute an already pending or completed side-effecting tool call.
Actual behavior
One recorded tool call resulted in seven real thread/start operations and seven persisted top-level tasks.
Sanitized host-log evidence
The values below are deliberately redacted. All seven resume requests referred to the same parent thread, active turn, client, and connection:
client_name="codex_chatgpt_android_remote"
client_version="dev"
connection_id=<SAME_CONNECTION>
thread/resume parent_thread=<PARENT_THREAD> active_turn=<ACTIVE_TURN> request_id=<R1>
thread/resume parent_thread=<PARENT_THREAD> active_turn=<ACTIVE_TURN> request_id=<R2>
thread/resume parent_thread=<PARENT_THREAD> active_turn=<ACTIVE_TURN> request_id=<R3>
thread/resume parent_thread=<PARENT_THREAD> active_turn=<ACTIVE_TURN> request_id=<R4>
thread/resume parent_thread=<PARENT_THREAD> active_turn=<ACTIVE_TURN> request_id=<R5>
thread/resume parent_thread=<PARENT_THREAD> active_turn=<ACTIVE_TURN> request_id=<R6>
thread/resume parent_thread=<PARENT_THREAD> active_turn=<ACTIVE_TURN> request_id=<R7>
The same remote connection issued seven new-thread requests during the incident window:
thread/start connection_id=<SAME_CONNECTION> client_name="codex_chatgpt_android_remote" request_id=<S1>
thread/start connection_id=<SAME_CONNECTION> client_name="codex_chatgpt_android_remote" request_id=<S2>
thread/start connection_id=<SAME_CONNECTION> client_name="codex_chatgpt_android_remote" request_id=<S3>
thread/start connection_id=<SAME_CONNECTION> client_name="codex_chatgpt_android_remote" request_id=<S4>
thread/start connection_id=<SAME_CONNECTION> client_name="codex_chatgpt_android_remote" request_id=<S5>
thread/start connection_id=<SAME_CONNECTION> client_name="codex_chatgpt_android_remote" request_id=<S6>
thread/start connection_id=<SAME_CONNECTION> client_name="codex_chatgpt_android_remote" request_id=<S7>
The parent rollout has only one creation call:
custom_tool_call name="exec" call_id=<ONE_CALL_ID>
input: tools.codex_app__create_thread({ prompt: <REDACTED>, target: { type: "projectless" } })
custom_tool_call_output call_id=<SAME_ONE_CALL_ID>
output: { "threadId": "<FIRST_CREATED_THREAD>" }
Suspected cause
The confirmed boundary is the Android Remote/app-server thread lifecycle, not model-level intentional delegation:
- The model-side parent rollout recorded one creation tool call.
- The Android Remote connection sent seven resumes and seven starts.
- Seven independent threads were actually persisted.
The likely failure mode is that repeated thread/resume handling at the Remote application/event layer replayed or redispatched the pending side-effecting create_thread action. This is not evidence of raw TCP packet retransmission. The create-thread tool does not expose an idempotency key, and the receiving path did not deduplicate the repeated execution using the original tool call ID.
The logs establish the repeated request sequence and persisted effects. They do not expose enough closed-client implementation detail to identify the exact Android callback or event-replay code path.
Suggested fix
- Treat the original tool call ID or a generated operation ID as an idempotency key for new-thread creation.
- Do not redispatch pending/completed side-effecting tool actions when an active thread is resumed.
- If the same create operation is received again, return the originally created thread ID instead of issuing another
thread/start. - Add a regression test covering repeated
thread/resumewhile acreate_threadtool call is in progress.
Privacy note
This report intentionally omits:
- account identifiers and email addresses;
- Windows username and home directory;
- project names and repository paths;
- complete thread, turn, message, call, and installation IDs;
- the original user prompt and transcript;
- raw rollout files,
logs_2.sqlite, and authentication/configuration files.
Sanitized excerpts can be provided privately if maintainers need request ordering or timestamps.
4 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
I found a second, separate Realtime Voice incident with the same one-call-to-many-persisted-tasks signature, on a later Codex core build.
Environment
realtime_voicesubagent0.146.0-alpha.9.2(recorded directly in the parent rollout)26.727.51351client_managed_handoffs: falseObserved correlation
All three cases occurred in the same Voice parent session:
| Recorded
create_threadcalls | Thread IDs returned by the tool | Additional same-turn resume responses | Persisted child tasks | Creation burst ||---:|---:|---:|---:|---:|
| 1 | 1 | 10 | 11 | 1.476 s |
| 1 | 1 | 10 | 11 | 1.901 s |
| 1 | 1 | 9 | 10 | 1.806 s |
For each batch:
create_threadcall ID and one tool output.I also observed a likely related delivery symptom: identical child-result payloads were delivered back to the Voice parent repeatedly. Across the three batches, 11, 10, and 11 unique result payloads appeared as 60, 67, and 60 parent messages respectively; a single payload arrived up to 7–8 times.
Evidence boundary: this build’s retained local logs do not expose per-request
thread/startlines for this incident, so I can confirm repeated resume responses and persisted effects, but not directly map each resume to a specific start request. I also cannot identify the exact failing component from these artifacts.I can provide sanitized UTC windows, call/turn identifiers, and narrow host-log excerpts privately if useful.
I second this. I've had the exact same issues repeatedly across multiple sessions.
I reproduced the same class of failure while a parent task was running on a Windows ChatGPT/Codex desktop host on 2026-08-07.
Important client-path clarification: the source task may have been opened from an iPhone around the time the duplicates were created. I cannot therefore characterize this as a confirmed desktop-only reproduction. The available task transcripts establish the duplicated execution, but they do not by themselves prove whether an iPhone Remote resume/reopen triggered the replay. This may be closely related to the Remote lifecycle described in the original report.
Observed behavior:
Expected behavior: one delegation/create operation should persist and execute exactly one child task. Opening, resuming, or reconnecting to the parent from another client must not replay a pending or completed side-effecting task-creation operation. A repeated request should return the original child task through an idempotency key rather than starting another task.
For privacy, I am not posting the parent/child task IDs, repository name, paths, prompt, or raw transcripts publicly. Those identifiers can be supplied through an OpenAI-controlled private/in-app feedback channel if maintainers need to correlate telemetry.