Skip to content

[Windows][WSL] Send button stays disabled after the first message (no turn/start is sent) #46775

Open

Description

@JonyanDunh

What happens

On Windows with WSL2 mode enabled ([desktop] runCodexInWindowsSubsystemForLinux = true), a thread only accepts the first message. After that turn finishes, the send button stays greyed out and clicking it does nothing.

  • The first message sends and completes normally.
  • Then the send button is disabled. Typing still works, but nothing can be submitted.
  • The unsent text stays in the composer as a draft (persisted in composer-prompt-drafts-v2 in the app state).
  • Restarting the app does not help. Re-opening the thread (which triggers a successful thread/resume) does not help either.
  • Workaround that does work: switch to another thread and back, or start a new thread.

Environment

  • Codex desktop app (Windows, Microsoft Store): 26.915.31945
  • app-server: 0.155.0-alpha.9.2
  • Windows 11 + WSL2 (Ubuntu 24.04)
  • Agent mode: full access

Key observation

After the first turn completes, no turn/start request is ever sent to the app-server — the message never leaves the UI. So this is a client-side disabled button, not a failed request.

The app log shows turn events being dropped by the renderer, because the conversation lookup uses a turn id where a thread id is expected:

error [electron-message-handler] Received turn/started for unknown conversation conversationId=01a0bdb0-cfc6-...
error [electron-message-handler] Received turn/started for unknown conversation conversationId=01a0bdb0-d152-...
error [electron-message-handler] Received turn/completed for unknown conversation conversationId=01a0bdb0-d152-...

Because those events are dropped, the per-thread "busy" flag is never cleared. The busy check in the WebView bundle (app.asar, decompiled) is:

if (unconfirmedTurnSubmissions?.some(e => e.terminal !== true)) return true;
if (resumeState === 'needs_resume') return threadRuntimeStatus?.type === 'active';
if (turns.length === 0) return resumeState === 'resuming';
return lastTurn(turns)?.status === 'inProgress';

Any of these being true disables the composer. The resume response already reports the turn as completed, so the state is stuck on one of the other branches.

Timeline (UTC)

Time Event
07:21:17 1st message: turn/start sent, turn runs
07:23:32 1st turn completes (task_complete)
07:24 – 07:42 18 minutes with zero turn/start requests, while the user was typing and clicking send
07:33:17 thread/resume succeeds (assignedStreamRole=owner), button still disabled
07:40 user switches to another thread and back
07:42:50 2nd message finally sends (turn/start, took 2.3 s)

Possibly related

WSL-mode latency is much worse than Windows-native here: turn/start takes ~2.3 s, one thread/resume took 17 s, and the app-server request queue overflowed with App server request expired while queued (-32001). A single turn also logs hundreds of [spawn-async] Spawning process inside WSL lines, which may be related to #46703 (concurrent wsl.exe probes crashing the Interop server).

Regression

Sessions from 2026-09-17 (app 26.911) contain many messages in one thread. Since 2026-09-19 (app 26.915), every thread gets exactly one message and then the send button is disabled.

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingwindows-osIssues related to Codex on Windows systems

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions