Older resumed Codex Desktop threads can miss newly available tools and may stay on legacy subagent runtime

Open 💬 6 comments Opened Jun 3, 2026 by Allmight97
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

I asked Codex to create this issue, hence the verbose and tech-dense tone. Intent is to capture as much of the info as I could about the issues I experienced today with sub-threads and (going back weeks) sub-agents. I wonder now if my existing sub-agent issues are related to the below problem.

Why this matters beyond a technical puzzle: I was under the impression new features were broken or the agent wasn't able to plan/stage them well. But performance was better in fresh threads that (so far today ) aren't exhibiting sub-agent nor the sub-thread handling tools.

I'll let codex take it from here:
---

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

Codex Desktop, current app build as of 2026-06-03. I do not want to overstate the exact About-dialog version here; the most useful repro anchors are the session IDs below.

What subscription do you have?

ChatGPT Pro ($100).

What platform is your computer?

macOS / Darwin on Apple Silicon.

What issue are you seeing?

Older resumed Codex Desktop threads appear able to miss newer tool surfaces after the Codex app/runtime is updated.

In a newer/current thread, tool_search can discover thread-management tools such as create_thread, list_threads, send_message_to_thread, set_thread_pinned, set_thread_archived, and set_thread_title. Calling create_thread from that newer thread succeeds.

In an older thread, the same class of tool discovery failed: create_thread was not available, and tool_search returned no thread-management tool. This made the older thread report that it could not create a separate review thread.

This looks like more than a one-off missing tool. It appears that older resumed threads may remain pinned to the tool/runtime metadata that existed when the thread was created, rather than refreshing to the current Codex app tool surface.

There is also a related suspicion around subagents: older threads may be negatively affected as Codex updates the multi-agent/subagent runtime. Current/newer threads can use the newer subagent tool surface, but older resumed/forked threads may fall back to legacy multi-agent behavior.

What steps can reproduce the bug?

Known session anchors:

  • Current/newer session where thread tools were discoverable and create_thread worked: 019e8a4c-6f76-7fc3-9a21-ec5e722c1b9c
  • Older session with the aforementioned missing thread-management tools issue: 019e4748-30f8-7622-af3f-bdb6b4997771

Observed flow:

  1. Open/resume an older Codex Desktop thread that was created before recent thread-management/dynamic-tool features were available.
  2. Ask it to create or use a separate review/background thread.
  3. The thread reports that create_thread is unavailable and that tool_search returned no thread-management tool.
  4. Open/use a newer thread in the same app/account context.
  5. Ask it to search for thread-management tools.
  6. tool_search exposes the codex_app thread-management tools.
  7. Calling create_thread succeeds.

Control result from the newer/current thread:

  • tool_search returned thread-management tools.
  • create_thread created a background test thread successfully.
  • spawn_agent also worked in the newer/current thread, which suggests the app is globally capable of these tools and the issue is likely per-thread/runtime state rather than a global account/app outage.

What is the expected behavior?

Older resumed threads should not silently lose or miss newer Codex Desktop tool capabilities after the app/runtime is updated.

One of these behaviors would be reasonable:

  1. On thread/resume, refresh safe host-owned dynamic tools to the current app surface when the saved thread metadata predates those tools.
  2. Provide a clear “continue/fork with current tools” path for legacy threads.
  3. Show an explicit warning when a thread is pinned to a legacy tool/runtime surface.
  4. For subagents, avoid silently falling back to an older multi-agent runtime unless that is required for correctness, and make the fallback visible.

Additional information

I searched existing openai/codex issues for related terms before filing and did not find a direct duplicate. Searches included terms around dynamicTools, tool_search, create_thread, thread-management tools, resumed/older threads, subagents, and multi-agent V1/V2 behavior.

Technical details from source inspection

I inspected current openai/codex source at commit:

e7039f98449d0e39687aab14eda4b4383b3bce31

Dynamic/thread-management tools appear thread-start scoped

thread/start accepts dynamicTools, validates them, and passes them into the new thread/session:

On session spawn, dynamic tools are restored from the resumed conversation history if no fresh dynamic tools are supplied:

// Dynamic tools are defined at thread start and persisted in rollout session metadata.
let dynamic_tools = if dynamic_tools.is_empty() {
    conversation_history.get_dynamic_tools().unwrap_or_default()
} else {
    dynamic_tools
};

Source:

InitialHistory::get_dynamic_tools() only reads SessionMeta.dynamic_tools from resumed/forked history:

The public app-server docs also describe dynamicTools as a thread/start API and say deferred dynamic tools become searchable through tool_search:

I did not see equivalent dynamicTools fields on thread/resume or thread/fork. That suggests older threads whose saved SessionMeta predates newer dynamic tools may resume with an empty/stale dynamic-tool set.

Subagent suspicion: explicit legacy multi-agent fallback for older resumed/forked threads

The subagent issue may be the same class of problem, though it is mediated through multi_agent_version rather than dynamicTools.

resolve_multi_agent_version() explicitly keeps resumed/forked threads without runtime metadata on legacy V1:

conversation_history
    .get_multi_agent_version()
    .or(inherited_multi_agent_version)
    .or(match conversation_history {
        InitialHistory::New | InitialHistory::Cleared => None,
        // Threads created before runtime metadata existed keep the legacy V1 tool surface.
        InitialHistory::Resumed(_) | InitialHistory::Forked(_) => Some(MultiAgentVersion::V1),
    })

Source:

The lookup for saved multi-agent runtime comes from SessionMeta.multi_agent_version or prior turn context metadata:

The planner then selects different visible/callable subagent tool families for V1 vs V2:

  • V2: direct tools such as spawn_agent, send_message, followup_task, wait_agent, close_agent, list_agents
  • V1: legacy/namespaced tools under multi_agent_v1, including spawn_agent, send_input, resume_agent, wait_agent, close_agent

Relevant source:

Existing tests appear to encode this behavior:

This may be intentional for compatibility, but it creates a confusing user-facing behavior: old threads can appear to have broken or missing subagent capabilities after the app has moved on to the V2 runtime.

Why this is worth addressing

The current behavior is difficult for users and agents to diagnose. A capable current thread can use tools successfully, while an older thread in the same app/account says the tools do not exist. That makes it look like a transient tool registry failure rather than a legacy-thread capability surface.

A small UX/protocol improvement would help a lot:

  • refresh safe host-owned tools on resume when missing,
  • add an explicit upgrade/fork-with-current-tools path,
  • or expose a clear legacy capability warning in the thread context/tool instructions.

Even if the legacy fallback is required for correctness, surfacing it would prevent agents from making incorrect claims like “the create_thread tool is not available in this session” when the more precise statement is “this resumed legacy thread did not receive the current host-owned thread-management tools.”

View original on GitHub ↗

6 Comments

github-actions[bot] contributor · 2 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #24808
  • #25727
  • #25285

Powered by Codex Action

joshka · 2 months ago

I think the broader gap here is runtime self-knowledge, not only refreshing dynamicTools.

Right now the assistant's understanding of its available capabilities appears to be mostly established at thread creation: initial prompt/context plus the initial session_meta.dynamic_tools. If the thread is later resumed after a version update, continued in a different harness, or moved between CLI/TUI/App/remote surfaces, the assistant may not have a reliable way to ask "what am I running inside now, and what can this current harness provide?"

A useful primitive would be a small always-available cross-harness introspection tool, something like runtime_status / capabilities / "tell me about myself". It could report:

  • current harness/surface and version
  • current thread origin and whether the tool surface is pinned to creation-time metadata
  • currently callable tool groups
  • available-but-not-loaded capability groups, such as App tools, MCP servers, plugins, skills, automations, browser, thread-management
  • whether a refresh/reload is possible
  • why an expected capability is absent, for example not supported in this harness, not loaded, auth failed, disabled by policy, stale thread metadata, or only available in fresh/App-created threads

That seems different from simply adding more tool metadata to the initial prompt. The point is to keep the initial context smaller and less stale while giving the model a stable way to query the current runtime when it needs to reason about capabilities.

dynamicTools may be part of the answer for callable tool injection, but this feels like a more general self-knowledge contract across harnesses and versions.

RIX-Coding · 1 month ago

Confirming that this is still reproducible in Codex Desktop 0.144.2 on macOS 26.5.2.

Feedback was submitted from the affected conversation with the session attached.

Feedback/session ID:
019f627a-df96-74d2-afe9-cd8f8a416fff

The affected resumed thread does not receive the codex_app thread-management tools, including create_thread, list_projects, read_thread, fork_thread, and send_message_to_thread.

A newly created control thread in the same Codex installation does receive the complete codex_app namespace. Its local thread ID is 019f6562-a2df-7080-8545-8de4dec07a9e, but that control session was not separately uploaded through feedback.

The practical impact is that the resumed thread cannot create a visible Codex task in a worktree and attempted inappropriate fallbacks such as a collaboration subagent, codex exec, Computer Use, and the local app-server protocol.

brianmulder · 1 month ago

not a fix, but an example work around when wait_threads arrived recently.
i didn't want to turf the thread but using 10-minute waits was getting annoying.

1. Start a waiter thread/task:

You are wait helper for coordinator <COORDINATOR_TASK_ID>.

You do not manage work. You do not judge results. You do not message workers
or change anything.

Check you have codex_app__wait_threads and codex_app__send_message_to_thread. If either is missing,
say UNSUPPORTED and stop.

Coordinator sends full list:

WAIT_SET generation=<n> REPLACE
- <role>: threadId=<id> hostId=<id>

Newest generation replaces old list. Never guess tasks.

Wait on exact list. Commentary and timeout mean nothing; keep waiting.

When a task completes or needs attention:
- snapshot every task in list;
- send coordinator trigger, status, and latest cursors;
- stop until coordinator sends next full generation.

If tool fails, send RELAY_FAULT once and stop.

Expire when the run ends or coordinator is replaced.

2. Connect coordinator to waiter:

Fresh task <waiter-task-id> has wait_threads. You do not.

It is only a wait helper. It has no work, review, sequencing, resource, or
decision authority.

Send it:

WAIT_SET generation=1 REPLACE

Include every current canonical task with role, threadId, and hostId.

It waits on exactly that list. When one task completes or needs attention, it
sends you the trigger plus a full snapshot and cursors.

After you act or change the task graph, send the whole list again as the next
generation. Never send a delta.

Keep the ten-minute heartbeat only in case this continuation path dies.
johnsilvavlogs · 1 month ago

Confirming this failure class again, this time with automation_update / thread heartbeats and an exact per-thread tool-surface comparison.

Environment:

  • Codex Desktop macOS release 26.721.41059, build 5848
  • App-bundled CLI 0.146.0-alpha.3.1
  • Affected thread: 019fa119-12f3-7bb1-8bf4-77d06df45065
  • Fresh/control thread: 019fa63f-d6a7-7fd2-92ec-012940eaeb5e

Observed behavior:

  1. The affected thread was asked to create a heartbeat for the next morning.
  2. It did not have the Codex host scheduling capability. It incorrectly substituted a goal/file edit, later admitted that a goal is not a heartbeat, and then attempted to operate the Codex Scheduled UI through Computer Use.
  3. A fresh peer thread in the same running app/account discovered and successfully called codex_app__automation_update, creating the requested heartbeat with ID marco-box-repair-follow-up and attaching it to the affected thread.
  4. The affected thread then visually saw that externally created scheduled task, confirming that the app/account/automation subsystem itself was healthy.

The saved rollout metadata makes the capability difference explicit. The affected thread's session_meta.dynamic_tools contains these 10 codex_app tools:

create_thread
fork_thread
list_projects
list_threads
navigate_to_codex_page
read_thread
send_message_to_thread
set_thread_archived
set_thread_pinned
set_thread_title

The fresh/control thread contains 16:

automation_update
create_thread
fork_thread
get_handoff_status
handoff_thread
list_projects
list_threads
load_workspace_dependencies
navigate_to_codex_page
read_thread
read_thread_terminal
send_message_to_thread
set_thread_archived
set_thread_pinned
set_thread_title
wait_threads

So automation_update was not merely hidden from the visible tool list; it was absent from the affected thread's deferred dynamic-tool registry. The control thread's successful call used the same Codex Desktop release and bundled CLI. Desktop logs also identify release 26.721.41059 for the affected session.

Impact: a long-running task that has all the live context needed for a monitor cannot schedule its own heartbeat after the host gains that tool. The model may then fabricate a non-equivalent goal, claim scheduling succeeded when it did not, or reach for fragile UI automation. The fallback behavior is an agent-quality problem, but the product-level trigger is the stale per-thread host tool surface.

Expected behavior: on resume/continue, refresh safe host-owned dynamic tools such as automation_update, or expose an explicit capability-upgrade/fork path and tell the agent that its tool surface is stale. A thread should not silently remain unable to create a heartbeat that the same app can create from a fresh peer thread.

This looks like the same root cause as this issue, with a concrete newer tool and user-visible automation failure. It may also be useful to cross-reference #29223 and #34433.

andybergon · 20 days ago

Confirming this with a creation-version and wrap-up A/B on macOS.

Environment:

  • ChatGPT Desktop 26.803.41515, build 6321
  • Current bundled Codex 0.147.0-alpha.6.5
  • Standalone Codex CLI 0.146.1, not the process hosting these Desktop tasks
  • Affected task created by Desktop under 0.147.0-alpha.1.2: 019fdce7-d9f6-78d3-a64f-f184c7dac796
  • Fresh projectless control created under 0.147.0-alpha.6.5: 019fde91-f414-7f20-9738-bf329557d088

Observed:

  1. The affected task's session_meta has no dynamic_tools field.
  2. At 2026-08-07T17:44:22Z, its ALL_TOOLS inventory contained none of:
  • codex_app__list_projects
  • codex_app__create_thread
  • codex_app__wait_threads
  1. Its local wrap-up title marker ran successfully later, at 18:03:19Z. After another real user message, the same three tools were still absent at 18:45:46Z. The marker therefore did not cause the missing tools.
  2. The fresh control persisted a codex_app dynamic-tool namespace containing all three controls. They were present before wrap-up and remained present after invoking wrap-up and sending a real follow-up message. Its total tool count stayed at 284.
  3. list_projects, create_thread, and wait_threads also executed successfully from a fresh peer task in the same running Desktop installation.

Current main still defines dynamic tools at thread start, restores the saved history set when no fresh set is supplied, and exposes no dynamic_tools field on ThreadResumeParams.

This rules out the standalone CLI version, local wrap-up invocation, title marker, archive state, and project context for this reproduction. The affected task is retaining an empty creation-time dynamic-tool snapshot when resumed under the newer Desktop runtime.

Expected behavior:

  • Refresh safe host-owned tools such as the codex_app namespace on resume.
  • Alternatively, add a capability-refresh or “continue with current tools” path.
  • Expose a clear legacy-tool-snapshot warning and current runtime capability status.