Codex Desktop side conversation cannot rename parent: set_thread_title stalls in thread/read host resolution
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
Summary
In Codex Desktop, a side conversation cannot reliably rename its parent task with the first-class set_thread_title tool. The parent exists, is visible in the sidebar, and remains active, but the tool stalls after a thread/read host-resolution preflight returns -32600 thread not loaded. No structured tool result is returned and the title remains unchanged.
This was reproduced three times from the same side conversation, across two renderer web contents.
Environment
- Codex Desktop:
26.903.71938(8576) - Bundled app-server:
0.153.4 - macOS:
26.6(25G72), Apple Silicon - Installed app: Developer ID signed by OpenAI, hardened runtime, stapled notarization ticket
app.asarSHA-256:58fef82480b9064e209b5b2fd934992e8d71515aea8084482369cfeaff1b8ee0
Minimal reproduction
-
Start a normal local Codex Desktop task (
<parent-thread-id>). -
Open a Desktop side conversation attached to that task (
<side-thread-id>). -
From the side conversation, invoke:
{"threadId":"<parent-thread-id>","title":"Renamed parent"}with the dynamic
set_thread_titletool. -
Observe that the parent remains present and usable in the sidebar, but the rename call never completes and its title does not change.
Expected behavior
Renaming should update persisted task metadata through thread/name/set, independently of whether the target conversation is currently loaded in a particular renderer/app-server runtime. If routing is genuinely ambiguous or unavailable, the dynamic tool should return one bounded structured error.
Actual behavior and sanitized evidence
Each attempt begins with dynamic_app_tool_renderer_execution_started tool=set_thread_title. It then issues multiple thread/read requests for <parent-thread-id>:
| Attempt | Renderer | First read | Second read | Observed tool result |
|---|---|---|---|---|
| A | 1 | success in 1 ms | -32600 thread not loaded in 864 ms |
none |
| B | 5 | success in 2 ms | -32600 thread not loaded in 646 ms |
none |
| C | 1 | success in 1 ms | -32600 thread not loaded in 632 ms |
none |
The failure records report timeoutMs=0. For all three call IDs, the log contains the execution-start record but no corresponding Sending server response record. There is also no thread/name/set for these attempts. The parent title remained unchanged when read back through the user-visible task API.
Near attempt B, renderer 5 also logged Received item/completed for unknown conversation for the parent. That supports a renderer-hydration aspect, but the rename should not depend on renderer conversation state.
Shipped-code path
I inspected the read-only extracted JavaScript from the signed installed bundle. The minified names below are included only to make the chain falsifiable:
- The
set_thread_titlehandler selectsarguments.threadId ?? sourceThreadIdand calls the rename helper. - The rename helper first calls a host resolver with only
scopeandthreadId; it does not passsourceHostId/preferredHostId. - With no preferred host, that resolver probes candidate host managers with
Promise.all(...), and each probe callsreadThread(threadId, ...). - Only after resolution does it call
setThreadTitle(..., {requireAcknowledgement: true}), which ultimately sendsthread/name/set. - The dynamic-tool dispatcher awaits the handler without an outer deadline and sends the final dynamic-tool response only after that await settles.
This explains both symptoms: an unnecessary live-read requirement blocks a persisted-metadata operation, and one unresolved candidate-host probe can prevent the handler's own catch from returning a structured error. The logs do not identify which candidate host remained outstanding, so the exact outstanding host is an inference; the unbounded fan-out and missing final response are directly observable.
The app-server rename implementation itself does not need a loaded runtime. It normalizes the ID/name and updates thread metadata directly:
Current public dynamic-tool plumbing also has unbounded response awaits (receiver.await / rx_response.await.ok()), compounding the missing-completion failure mode.
Related issues / duplicate search
I searched open and closed issues and PRs for set_thread_title, thread/name/set, thread/read, thread not loaded, side-chat rename/title, and unknown conversation. I did not find an exact report of a side conversation failing to rename its parent.
- #34295 is the closest liveness relative: a local Desktop dynamic-tool call stalls while an unrelated remote SSH host reconnects. It suggests this cross-host fan-out problem predates the current build.
- #42509 and #42286 describe side-chat/subagent renderer hydration failures (
unknown conversation), but not title mutation. - #37668 reports thread-management tools hanging before reaching
AppServerConnection; here the rename does reach it and stalls in preliminarythread/readrouting. - #40777 describes a related missing-completion pattern in nested tool dispatch, but not this API path.
- #40094 and #43492 concern different title-attribution/shortening behavior.
Proposed fix
- Preserve target-host provenance in
set_thread_title:- accept an explicit
hostIdwhen provided; - use
sourceHostIdfor self/parent operations when the side-conversation provenance establishes that host; - otherwise resolve from stable sidebar/catalog summary metadata rather than requiring a live
thread/read.
- accept an explicit
- Once the host is known, send
thread/name/setdirectly. Do not require the target conversation to be loaded or hydrated. - If multiple hosts truly match, return a bounded, explicit ambiguity error.
- Put per-host deadlines around discovery and use early-success / settled-result handling so one unavailable host cannot block a local match.
- Add a dispatcher-level timeout/cancellation path that always emits exactly one structured
DynamicToolCallResponseand removes pending-call state.
Regression tests
- An ephemeral side conversation renames an unloaded-but-persisted parent on the same local host; a
thread/readstub may returnthread not loaded, but exactly onethread/name/setis sent and the rename succeeds. - An unavailable/reconnecting remote host is registered while the local parent is targeted; the local rename completes without waiting for the remote host.
- Omitted
threadIdself-rename usessourceHostIdand does not fan out. - Parent events are unknown in the side renderer; persisted rename still succeeds and sidebar/header converge when hydrated.
- Genuine multi-host ambiguity returns a bounded structured error naming the ambiguity.
- An injected host-read or handler hang produces a timeout response and cleans up the pending dynamic call exactly once.
Workarounds
- Safest: rename manually from the parent task's sidebar/context menu.
- Often effective: navigate to the parent task, let it hydrate in the current window, and invoke the rename from the parent task itself.
- Retrying from the side conversation after merely opening the parent is best-effort only because the side renderer can still perform cross-host discovery.
No SQLite state, installed app files, or application processes were modified during this investigation.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with codex-rs/app-server/src/request_processors/thread_processor.rs#L1832-L1864, then trace the set_thread_title handler, host resolver, and dynamic-tool dispatcher described in the report. Add coverage for unloaded-parent renames, unavailable hosts, ambiguity, and hung calls, verifying bounded responses, cleanup, and exactly one thread/name/set request.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, rust
- Domain
- backend-api-design, desktop, devtools
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100