Codex App create_thread returns pendingWorktreeId without an addressable threadId
What version of the Codex App are you using (From “About Codex” dialog)?
26.602.40724 (3593)
What subscription do you have?
Unknown from the agent/tool context. This was observed inside Codex App while using thread-management tools.
What platform is your computer?
Darwin 25.5.0 arm64 arm
What issue are you seeing?
When an agent creates a new Codex-managed worktree thread through the app thread tool, create_thread may return only a pendingWorktreeId, for example:
{
"pendingWorktreeId": "local:<uuid>"
}
At that point the orchestrating thread has no reliable way to address, inspect, amend, cancel, or monitor the pending child.
The available follow-up tools require a threadId, but the create result does not expose one yet. There also does not appear to be a tool to map pendingWorktreeId -> threadId, send a message to a pending worktree, read pending setup status, or cancel the pending setup.
This creates an orchestration gap:
- The parent can create a managed worktree request.
- Before the child materializes as a normal thread, the parent cannot update its instructions.
- If the child never becomes discoverable through
list_threads, the parent has no reliable way to recover. - If the initial prompt omitted an important instruction, the parent cannot amend it until the thread is discoverable.
This is particularly painful for multi-thread workflows where a parent/orchestrator thread is creating worker threads for repo work and needs to coordinate follow-up instructions, stop conditions, or cancellation.
Related but broader feature request: #14923. This issue is narrower: the pending worktree object returned by create_thread is not addressable from the thread-management tool surface.
What steps can reproduce the bug?
- From a Codex App thread with thread-management tools available, create a new project thread using a Codex-managed worktree environment:
{
"target": {
"type": "project",
"projectId": "/path/to/saved/project",
"environment": {
"type": "worktree",
"startingState": {
"type": "branch",
"branchName": "some-existing-branch"
}
}
},
"prompt": "Start work in the managed worktree and report back."
}
- Observe that the tool may return only:
{
"pendingWorktreeId": "local:<uuid>"
}
- Try to send an additional instruction before the new worktree thread appears.
Actual:
send_message_to_threadcannot be used because there is nothreadId.read_threadcannot be used because there is nothreadId.list_threadsmay not reveal the pending child yet, and there is no guaranteed mapping frompendingWorktreeIdto the eventual thread.- There is no apparent
read_pending_worktree,cancel_pending_worktree, orsend_message_to_pending_worktreetool.
- If the parent realizes the initial prompt needs correction, it cannot reliably amend or cancel the pending child.
What is the expected behavior?
One of these would make the workflow reliable:
create_threadreturns bothpendingWorktreeIdand a reserved/futurethreadIdthat can receive queued messages or cancellation.- A pending-worktree API exists:
read_pending_worktree(pendingWorktreeId)cancel_pending_worktree(pendingWorktreeId)send_message_to_pending_worktree(pendingWorktreeId, prompt)
- A mapping API exists:
resolve_pending_worktree(pendingWorktreeId) -> { status, threadId? }
- The app emits a parent-visible completion event when the pending worktree materializes, including the new
threadId. list_threadscan search bypendingWorktreeIdand reliably return the eventual thread once available.
The key requirement is that the object returned by create_thread remains addressable through the tool surface until it either becomes a real thread or fails/cancels.
Additional information
This is not just a UI polish issue. It affects orchestration reliability:
- Parent/orchestrator threads need deterministic child lifecycle control.
- A pending child may need amended instructions before first run.
- A pending child may need cancellation if the parent discovers the branch/worktree setup was wrong.
- Without a
threadIdor pending-worktree control surface, the parent is forced to wait and hope the child appears in thread search.
Workaround:
- Put every possible instruction into the initial
create_threadprompt. - Wait for the child to materialize and try to discover it through
list_threads.
That workaround is brittle because the parent cannot correct or cancel the pending request during the setup gap.
3 Comments
Reproduced from
jnyross/codex-graphLisbon v4 dogfood (Codex Desktop, 2026-08-09).create_threadfor aworktreeenvironment returned only aclientThreadIdwith nothreadId, and there was no tool to resolve, inspect, or cancel the pending setup. The orchestrator had to polllist_threadswith heuristics for 120s; the pending rows never appeared and the parent could not recover or learn why.Terminal evidence:
mac-vm-harness--codexgraph-test/candidates/frozen-lisbon-v4/terminal-lisbon-v4.jsonpreserves all threeclient_thread_idhandles inpending_setupstate. This is the sameclientThreadId/pendingWorktreeIdaddressability gap described here.The skill workaround (PR #14, v0.4.5) now preflights Git repository status and avoids placing read-only workers in worktree environments, but a real pending-worktree control surface is still needed for two-writer workflows.
+1 need a way to resolve
clientThreadIdintothreadIdReproduced on Codex Desktop for macOS on 2026-08-19, with a concrete duplicate-work consequence.
An orchestrator called
create_threadfor a project worktree at 18:18:25Z. The call was accepted and returned only{"clientThreadId":"client-new-thread:<uuid>","hostId":"local"}.The eventual task record shows that it actually started at 18:18:28Z, about three seconds later. However, repeated
list_threadschecks at approximately +3s, +8s, +18s, +33s, and +64s did not provide a way to correlate that acceptedclientThreadIdwith the materialized task.Because the accepted operation was not addressable and could not be resolved, waited on, inspected, or cancelled, the orchestrator incorrectly retried
create_thread. That retry was also accepted and created another real worker. A later replacement created during attempted recovery produced a third worker. All three ran concurrently and opened separate draft PRs before the duplicates were detected and stopped.The eventual task-list records were also unsuitable for correlation: affected entries could have
projectId: null, a null or unusable title, and a Codex-managed worktreecwd. TheprojectIdsymptom appears related to #37021, while the duplicate consequence is closely related to #36592.This is a recurrent tool-contract trap rather than only an agent-prompt problem:
clientThreadIdlooks like an accepted asynchronous-operation handle, but no subsequent tool accepts it. A robust contract needs one of:threadIdplus lifecycle state;resolve,status,wait, andcanceloperations acceptingclientThreadId; orlist_threadscarrying the submitted correlation ID and lifecycle state.Retry safety would also benefit from an idempotency key or server-side deduplication. Until then, callers cannot reliably distinguish slow setup from lost setup, and retrying an apparently missing task can consume substantial tokens and compute.
Temporary workaround
I published the local PreToolUse/PostToolUse guard used to prevent this retry pattern, with installation instructions, limitations, and regression tests:
https://gist.github.com/dsfaccini/667c951c03b04df42c87dac2cbaddd86
The guard is intentionally presented as a temporary fail-open mitigation, not as a substitute for an addressable lifecycle or idempotent creation contract.