send_message_to_thread intermittently fails to start a turn when the target thread is idle

Open 💬 1 comment Opened Aug 14, 2026 by sdro

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

26.803.61601 (build 6396)

What subscription do you have?

Pro

What platform is your computer?

macOS 26.5.2 (25F84), Apple Silicon Darwin 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:28:34 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T6041 arm64 arm

What issue are you seeing?

A Codex side chat intermittently cannot send a follow-up message to an idle main thread using send_message_to_thread.

The main thread and side chat are displayed in the same Codex window. The target thread is local and reported as idle. The call uses the correct thread ID and hostId: "local", but returns:

Cannot steer conversation <redacted-thread-id> because its active turn already ended

The operation should not fail merely because the destination is idle. The same cross-thread operation has succeeded on other attempts, making delivery unreliable and state-dependent.

No thread IDs, prompts, project paths, or other personal information are included in this report.

What steps can reproduce the bug?

  1. Open an existing Codex thread.
  2. Open a side chat associated with that work.
  3. Allow the main thread's active turn to finish so the thread is idle.
  4. From the side chat, ask Codex to send a benign follow-up to the main thread.
  5. Codex invokes the equivalent of:

``json
{
"threadId": "<redacted-main-thread-id>",
"hostId": "local",
"prompt": "Benign follow-up message"
}
``

  1. Observe that send_message_to_thread can return:

``
Cannot steer conversation <redacted-thread-id> because its active turn already ended
``

  1. The intended new turn does not begin.

The behavior is intermittent. The same tool and destination can work on other attempts.

What is the expected behavior?

send_message_to_thread should handle both destination states:

  • If the destination has an active turn, deliver the message using the supported active-turn follow-up behavior.
  • If the destination is idle, start a normal new turn.

A race in which the destination finishes while delivery is being prepared should also fall back safely to starting a new turn instead of returning an inactive-steer error.

Additional information

Inspection of the JavaScript bundled with Codex App 26.803.61601 suggests a likely error-handling defect:

  1. The send-follow-up-message handler first attempts the steer path.
  2. If that throws SteerTurnInactiveError, the handler is intended to catch it and start a normal turn.
  3. The follower/owner request bridge reconstructs returned errors using the equivalent of Error(i.error).
  4. That reconstruction discards the original error name.
  5. The fallback predicate recognizes either an error named SteerTurnInactiveError or a message containing that identifier.
  6. The surfaced message contains only “Cannot steer conversation … because its active turn already ended”, so the predicate may not recognize it and the fallback does not run.

This is a likely explanation for the intermittent behavior, although I did not capture internal ownership telemetry for the failed call.

Suggested fix:

  • Preserve structured error codes or names across the follower/owner bridge.
  • As defensive handling, recognize the inactive-turn message and fall back to starting a normal turn.
  • Retain the fallback even if destination state is checked earlier, because the active turn can finish between the check and delivery.

Related but apparently not duplicate:

View original on GitHub ↗

1 Comment

victorherbst · 15 hours ago

Additional reproduction from Codex Desktop 26.820.60940 (build 7119) on macOS 26.5.1 (25F80), arm64.

This appears to be the same inactive-turn fallback bug, with an automation/heartbeat and UI-state desynchronization dimension:

  1. A thread targeted by an hourly heartbeat appeared in the UI as “Working for 27m 43s” while showing steps such as reading automation.toml.
  2. Thread inspection from another task reported the target as idle. Its latest scheduled turn had already completed successfully in 75.18 s, but it had no visible final assistant message.
  3. Three consecutive send_message_to_thread attempts used the correct local target and hostId: "local", but all failed with:

``text
Cannot steer conversation <redacted-thread-id> without an active turn id
``

  1. Navigating the app to the target thread and refreshing its status did not recover delivery.
  2. A later hourly heartbeat was able to create and complete another turn. After that scheduled wake, the same cross-thread follow-up operation succeeded again without changing the target ID or host.

No task IDs, project paths, prompts, or user content are included here.

Expected behavior:

  • When the destination is idle, send_message_to_thread should start a normal turn instead of trying only the steer path.
  • If a scheduled turn completes without a final assistant message, the UI should stop displaying “Working” and reconcile to the backend-completed state.
  • A heartbeat transition should not be required to repair cross-thread delivery.

This strengthens the race/fallback hypothesis in the original report and suggests the Desktop UI may also retain stale “Working” state when a scheduled turn completes without a final message.