Desktop: "Error submitting message" — turn/start times out (30s) while app-server sidecar stalls silently for minutes

Open 💬 5 comments Opened Jun 10, 2026 by malikdraz

What version of the Codex App are you using?

26.608.12217 (codex core 0.138.0-alpha.7, originator Codex_Desktop, auth mode ChatGPT)

What subscription do you have?

<!-- FILL IN: e.g. ChatGPT Pro / Plus / Team -->

What platform is your computer?

macOS 26.5.1, Apple Silicon (arm64)

What issue are you seeing?

Submitting a message in a Desktop thread intermittently fails with the inline banner "Error submitting message". Re-sending the exact same message a minute or two later succeeds.

Desktop renderer log shows the composer giving up after a 30-second RPC timeout on turn/start:

2026-06-10T13:05:09.164Z warning [electron-message-handler] mcp_request_timeout
    conversationId=019eb19c-84da-7933-a03c-cbfbb4e11d57 method=turn/start
    pendingCount=13 requestId=654d3396-... timeoutMs=30000
2026-06-10T13:05:09.173Z error [electron-message-handler] [Composer] submit failed
    cwd=~/Documents/Codex/2026-06-10/... errorMessage=Timeout errorName=Error
    errorStack="Error: Timeout\n at Object.error (app://-/assets/vscode-api-....js:...)\n at ks (app://-/assets/composer-....js:...)" followUp=local mode=local

7 occurrences on 2026-06-10 (first at 10:48:42Z), zero occurrences in the preceding 13 days of desktop logs. All 7 timeouts are method=turn/start; pendingCount grows across the day (2 → 5 → 6 → 13), so requests pile up against the same app-server sidecar.

Root-cause evidence from the app-server side (~/.codex/logs_2.sqlite, logs table): the main app-server sidecar process stops emitting any log rows at all for multiple minutes, and every submit failure falls inside one of those silent windows. Example: the sidecar wrote ~1000 rows during 23:04 local while a turn was streaming, then wrote nothing from 23:05 through 23:11 — exactly bracketing three consecutive submit timeouts at 23:05:09, 23:06:50 and 23:10:33 (UTC+10 local; 13:05/13:06/13:10Z in the desktop log above).

The stalled thread's previous turn never finalizes: e.g. thread 019eb19b-dae6-7192-be60-8d0432f9587c logged its final answer and token usage at 12:57:27Z (needs_follow_up=false has_pending_input=false), then the thread never logged another line and never emitted turn/completed; the next turn/start for that thread (12:57:44Z) timed out at 12:58:14Z. The desktop also logs Received turn/started for unknown conversation for the ephemeral title-generation thread during the same window.

Possibly relevant: the stall windows coincided with elevated concurrency on the shared ~/.codex SQLite databases — 3–4 distinct codex processes (per process_uuid in the logs table) writing to logs_2.sqlite at the failure minutes vs. 1 normally (nested codex exec runs spawned by an agent, plus agent turns querying ~/.codex/state_5.sqlite / logs_2.sqlite directly). logs_2.sqlite had grown to 1.4 GB with a multi-MB WAL.

Ruled out locally: plugin hooks (UserPromptSubmit/Stop hook binaries exit in <1 s when run by hand), the notify turn-ended client (unchanged binary), network-proxy feature (same log lines appear on prior, healthy days), leftover nested codex processes (none running; only the sidecar holds the DBs), and a newly added <name>.config.toml profile-v2 file (never loaded without --profile).

What steps can reproduce the bug?

Not deterministic, but reliably reproduced today with this pattern:

  1. Run an agent thread in the Desktop app that heavily reads ~/.codex itself (e.g. SQLite queries against a large logs_2.sqlite, rg over ~/.codex) and/or spawns nested codex exec / codex --profile <name> exec subprocesses, so several codex processes share the ~/.codex SQLite databases concurrently.
  2. While that turn is running (or just after its final answer), submit another message — either a follow-up in the same thread or a message in another thread owned by the same sidecar.
  3. If the sidecar is in one of its silent stall windows, the submit shows "Error submitting message" after ~30 s.
  4. Wait 1–5 minutes; resubmitting the identical message succeeds.

Affected thread/session ids from 2026-06-10 (in case server-side lookup helps):
019eb122-5dbf-7462-ac2d-a0ea9ac9a39f, 019eb12c-1a8d-7551-990f-768161dd4a28, 019eb12d-befb-71e0-88c2-e77aaeefc885, 019eb19b-dae6-7192-be60-8d0432f9587c, 019eb19c-84da-7933-a03c-cbfbb4e11d57

What is the expected behavior?

  • turn/start should be acknowledged promptly (or fail fast with a real error) even when the thread's previous turn is still finalizing or the sidecar is under I/O pressure; a queued/steered message should not surface a generic "Error submitting message".
  • A turn that has produced its final answer should always reach turn/completed; the per-thread session loop should not wedge silently with no log output.
  • Consider rotating/pruning logs_2.sqlite — unbounded growth (1.4 GB here) amplifies any DB-contention stall.

Additional information

  • Timezone note: desktop log timestamps are UTC; app-server sqlite analysis above is local (UTC+10).
  • Happy to provide redacted log excerpts or run diagnostics on request.

View original on GitHub ↗

5 Comments

twentyOne2x · 2 months ago

Adding a current-build datapoint that matches this issue and links it to broader Desktop live-state degradation: #27601.

Environment:

Codex Desktop 26.608.12217 / build 3722
Bundled CLI 0.138.0-alpha.7
macOS 26.3 / Darwin 25.3.0 arm64

Observed repeated turn/start timeouts while submitting a message in Desktop:

mcp_request_timeout method=turn/start pendingCount=86 timeoutMs=30000
mcp_request_timeout method=turn/start pendingCount=87 timeoutMs=30000
mcp_request_timeout method=turn/start pendingCount=86 timeoutMs=30000

The same thread later became readable and completed, so this was not data loss. The failure appears to be Desktop/app-server sidecar backlog or live-state wedging: retrying later may work, and a full Desktop quit/reopen makes sends work again temporarily.

YoneRai12 · 1 month ago

[app-server][Desktop][Windows][turn/start][thread/resume][notLoaded][Error submitting message]

I saw a closely related Windows Desktop variant that may be a narrower race than the sidecar stall described here.

Reproduction variant

  1. Have a stored Desktop thread that is present on disk but not currently loaded in the app-server runtime (notLoaded state).
  2. Open that long/stored thread in Codex Desktop on Windows.
  3. Submit a prompt immediately, before the thread load/resume path has visibly completed.
  4. Observed: the composer can surface Error submitting message. Fully restarting Codex Desktop clears the condition.

This is close to this issue and #23644 because restart clears the stale/unloaded runtime state. It is related to #30704 only in the broader Windows Desktop live-state class; #30704's direct symptom is active-turn interruption from opening a local file, so I would not treat it as the primary duplicate.

Root-cause hypothesis

In the public app-server code, turn/start currently calls TurnRequestProcessor::load_thread, which resolves the thread id and then requires thread_manager.get_thread(thread_id) to already have a loaded runtime handle. If Desktop sends turn/start while the UI has opened a stored thread but before thread/resume finishes, the app-server path can fail as thread not found even though the rollout exists on disk.

That error shape gives the Desktop client no structured way to distinguish:

  • a truly invalid/nonexistent thread id, from
  • a stored-but-not-yet-loaded thread that should be resumed before accepting composer submit.

Patch proposal, if maintainers want a PR

A small app-server contract improvement would be to return a structured retryable invalid-request error for this race, for example:

{
  "code": -32600,
  "message": "thread not loaded or not found: <thread_id>; call thread/resume and wait for it to complete before turn/start",
  "data": {
    "thread_error_code": "thread_not_loaded",
    "retry_method": "thread/resume",
    "blocked_method": "turn/start"
  }
}

The Desktop client could then disable/queue composer submission until thread/resume completes, or retry after resume, instead of surfacing a generic Error submitting message.

The same structured error is useful for nearby methods that also need a loaded thread handle, such as turn/steer, thread/inject-items, thread/settings/update, review/start, and turn/interrupt.

Test coverage proposal

Add an app-server integration test that:

  1. creates a fake stored rollout,
  2. starts the app-server without calling thread/resume,
  3. sends turn/start for that stored thread id,
  4. asserts the full JSON-RPC error object includes thread_error_code=thread_not_loaded, retry_method=thread/resume, and blocked_method=turn/start.

This keeps the client-visible contract executable and avoids piecemeal field-only assertions.

Local validation attempted

  • cargo fmt --all --check from codex-rs: passed. It emits the existing stable-rustfmt warnings about imports_granularity = Item requiring nightly.
  • git diff --check: passed, with only Windows CRLF replacement warnings.
  • Targeted test command attempted: cargo test -p codex-app-server --test all turn_start_reports_resume_retry_for_unloaded_stored_thread.
  • First local run was blocked by Windows application control while executing a dependency build script.
  • A later run was blocked by local disk space exhaustion while compiling dependencies before the test body ran.

Per docs/contributing.md, I am posting this as issue analysis/reproduction/fix outline rather than opening an unsolicited external PR.

loremcdmx · 1 month ago

I hit this exact signature today and was able to trace it to a specific, reproducible cause: an orphaned codex_chronicle process holding the Chronicle lock (#19516).

Environment: ChatGPT.app 26.721.41059, Codex Framework 150.0.7871.128, macOS 26 (Darwin 27.0.0), Apple Silicon.

Identical renderer log to the one in the opening post:

[electron-message-handler] mcp_request_timeout method=turn/start pendingCount=12 timeoutMs=30000
[electron-message-handler] [Composer] submit failed errorMessage=Timeout errorName=Error followUp=local mode=local

And the same queue pile-up — inFlightRequestCount pinned at 6 with app/list, skills/list, config/read and thread/list all dying with App server request expired while queued.

The cause in my case: a codex_chronicle process (PID 1261, PPID 1) left over from an app instance four days earlier was still holding the lock, so the current app's sidecar could never start. It logged Waiting to acquire lock (owned by pid=1261) 5,074 times in one day.

kill 1261 alone fixed it — the waiting sidecar acquired the lock 20 seconds later:

| Window | submit failed | expired while queued |
|---|---|---|
| 13:38–14:06 (before kill) | 4 | 47 |
| 14:10–14:19 (after kill) | 0 | 0 |

So for anyone landing here with the intermittent "Error submitting message": before anything else, check for a stale sidecar from a previous app launch —

ps -axo pid,lstart,etime,command | grep codex_chronicle

— and see whether its start time predates your current app session. Details and suggested fixes in #19516.

This may not be the only path to the symptom described here (a sidecar can presumably stall for other reasons), but it is one concrete, verifiable one.

Audacity88 · 21 days ago

Current-build datapoint: this also occurs under severe host CPU contention.

Environment:

  • ChatGPT/Codex Desktop 26.730.61639, build 6234
  • Codex CLI 0.147.0-alpha.1.2
  • macOS 26.5.1 arm64

While two unrelated development daemons were consuming roughly 195% and 141% CPU, Codex's app-server queue stopped servicing even critical requests:

  • turn/start: 30-second timeout, pendingCount=16
  • config/read: 30-second critical-priority timeout
  • thread/read and thread/list: rejected after 60 seconds queued
  • queue state included six in-flight requests and 6-11 interactive requests

The failures occurred in both Approve for me and Full Access, including Error creating chat: App server request expired while queued. There was no stale codex_chronicle process.

I also observed 35 idle cua_node/bin/node_repl children under the app-server during the stall. After terminating the unrelated high-CPU processes, Codex recovered and chat creation worked again.

This suggests host CPU pressure can trigger or amplify an app-server scheduling/backpressure failure. Critical chat creation, configuration, and approval traffic should remain responsive, or background hydration should be shed earlier.

genzorr · 20 days ago

Adding a current-build macOS datapoint with a same-workspace A/B control that appears to match this issue.

Environment

  • Codex Desktop 26.730.61639
  • macOS
  • observed 2026-08-07
  • same local project/workspace for both threads below

This has become a frequent reliability problem over the last several days, primarily in one long-lived thread.

Same-workspace A/B

Affected thread turn/start response times progressed approximately as follows:

  • 26 ms at 09:36 UTC
  • 11,093 ms at 11:02
  • 38,109 ms at 11:45
  • 46,104 ms at 11:50
  • 46,580 ms at 11:58

For the latter three, Desktop emitted a 30-second mcp_request_timeout for turn/start, but the response eventually arrived and was routed. These request records showed queueWaitMs=0.

I then created a fresh thread in the same Desktop app / same project / same working directory. Its turn/start times were:

  • 33 ms at 11:48
  • 38 ms at 12:00
  • 139 ms at 12:14

So the strongest local observation is:

same project + same app + fresh thread: ~30–140 ms

versus

existing affected thread: ~38–47 seconds

This argues against general backend latency, the project directory itself, or a machine-wide Desktop slowdown, and points more toward thread-local app-server/session-runtime state or request serialization.

Compaction does not recover it

The affected thread underwent multiple context compactions. Token accounting showed compaction-like resets around ~234k -> 0 and ~259k -> 0.

Later, while the slow turn/start behavior was still occurring, the core log reported approximately:

total_usage_tokens=219733
auto_compact_scope_limit=244800
full_context_window_limit=258400
full_context_window_limit_reached=false

So successful compaction did not restore normal message-acceptance latency.

I also checked the public source at openai/codex@f2bee854a73666e1c3e922a853dda591b1a25fcf (2026-07-28). At that ref, app-server turn/start is serialized per thread, resolves configuration/environment/workspace-root state, then submits Op::UserInput through core. Pre-sampling compaction runs later inside admitted turn execution; it does not recreate the app-server per-thread serialization queue or live thread/session object. That makes it plausible for compaction to succeed while an independent thread-local acceptance/routing problem persists.

At the same pinned ref, runtime_workspace_roots are resolved/deduplicated as paths; I did not find a recursive repository scan in the turn/start path. The affected request configuration had many workspace roots, but the fresh-thread result in the same project is also evidence against project-wide scanning being the primary cause.

What would help distinguish the remaining causes

I have preserved the affected thread and local logs and can provide sanitized excerpts if useful. The smallest useful instrumentation would distinguish time spent in:

  1. app-server per-thread request serialization before turn_start_inner runs;
  2. awaited thread/config/environment work inside turn_start_inner;
  3. core submission/admission handling;
  4. Desktop response routing after app-server completion.

I don't think the exact internal blocker is proven yet, but the repeatable old-thread-vs-fresh-thread A/B makes this look like a thread-scoped runtime/acceptance bug class rather than ordinary model generation latency or repository size.