Skip to content

thread/resume is effectively quadratic on large active threads, blocking Remote steering #38787

Open

Description

@Luzivog

Summary

Opening or steering a very large active Codex thread from iOS Remote can time out even though pairing, authentication, transport, and the host app-server are healthy.

The same failure occurs through a client that sends a follow-up to an existing thread: the client first performs thread/resume, app-server parses the rollout successfully, then remains CPU-bound while composing the resume response. The follow-up is never submitted.

For the affected thread, using the already-loaded Codex Desktop composer bypassed thread/resume and delivered the steer immediately.

Environment

  • Codex CLI / app-server: 0.147.0
  • Codex Desktop: 26.810.50856
  • iOS Remote client: 1.2026.209
  • Host: Linux x86_64 (6.17.0-14-generic)
  • Rollout size when reproduced: 22,617 records, dominated by one long active turn with many tool and subagent items

Observed behavior

Each Remote attempt completed rollout parsing quickly:

Resumed rollout with 22617 items, parse errors: 0

It then stopped making progress at:

composing running thread resume response ... active_turn_present=true ... active_turn_status=InProgress

During the stall:

  • app-server consumed approximately one CPU core continuously;
  • RSS was approximately 1.1 GB;
  • the client eventually surfaced a generic failure;
  • the user prompt was absent from the target rollout, confirming that steering never began;
  • repeated iOS retries opened additional connections that all stalled at the same reconstruction point.

There were no concurrent 401, 403, duplicate-owner 409, pairing, or websocket-connectivity failures.

Reproduction

  1. Create or retain an active thread with a very large rollout, particularly one long turn containing thousands of tool/subagent items.
  2. Connect to the host through iOS Remote.
  3. Open that thread or send a prompt while its turn is active.
  4. Observe that the client initiates thread/resume with turn history included.
  5. Rollout parsing completes, but the resume response does not return in practical time and the steer is never submitted.
  6. Open the same already-loaded thread in Codex Desktop and use its composer/Steer control; the message is recorded and handled immediately.

The failure also reproduced through the Codex thread-management follow-up path because it performs the same full thread/resume before steering.

Root cause

In app-server/src/request_processors/thread_lifecycle.rs, the loaded-thread resume path calls populate_thread_turns_from_history whenever pending.include_turns is true:

https://github.com/openai/codex/blob/rust-v0.147.0/codex-rs/app-server/src/request_processors/thread_lifecycle.rs#L528-L567

That builds legacy API turns from every rollout item. During reconstruction, upsert_turn_item searches the growing item vector linearly for every incoming item:

https://github.com/openai/codex/blob/rust-v0.147.0/codex-rs/app-server-protocol/src/protocol/thread_history.rs#L1547-L1559

if let Some(existing_item_index) = items
    .iter()
    .position(|existing_item| existing_item.id() == item.id())

For a turn with thousands of items, repeated linear scans make reconstruction effectively quadratic. The stall begins after parsing and exactly where the logs say app-server is composing the running-thread response. The process behavior is consistent with CPU-bound reconstruction rather than a dead connection or authentication failure.

The same linear upsert was still present on main when checked on 2026-08-15.

Expected behavior

A Remote client should be able to open and steer a known active thread without material latency proportional to the square of its retained turn-item count. A failure should also be bounded and retryable rather than accumulating more expensive resume jobs.

Suggested fixes

Two complementary changes would address this:

  1. Metadata-only steering path: when the client already knows the thread and only needs its active turn ID, call thread/resume with excludeTurns: true, then submit turn/steer with expectedTurnId. Do not serialize full history merely to steer an active thread.
  2. Linear history reconstruction: maintain an item-ID-to-index map while building a turn, or otherwise avoid items.iter().position(...) for every item. Pagination/bounds would provide additional protection for unusually large histories.

It would also help to coalesce or reject duplicate in-flight resumes for the same thread so repeated mobile retries do not amplify the CPU queue.

Workaround

Use the already-loaded Codex Desktop composer and explicitly select Steer. This bypassed full history reconstruction and delivered the prompt immediately. Avoid repeated Remote retries on the affected thread.

Privacy note

This report intentionally omits the thread ID and full rollout because the rollout contains private project conversation and tool output. The two sanitized log lines above identify the failing stage without including that content. I can provide narrower diagnostics if maintainers specify what is needed.

Related but not exact duplicates

Activity

  1. mrcoinzzz commented on Aug 16, 2026

    @mrcoinzzz

    I can reproduce what looks like a related large-thread Remote failure, with a particularly strong iPad/iPhone comparison.

    Environment:

    • ChatGPT mobile app on iPad and iPhone 15 Pro Max
    • Same ChatGPT account
    • Same Wi-Fi/LAN
    • Same MacBook running the Codex host
    • Same Codex project and same Remote thread

    Observed behavior on iPad:

    1. ChatGPT works normally before opening Remote.
    2. Remote project list loads normally.
    3. Opening a short/new thread within the project usually works.
    4. Opening a long-established Remote thread is much more likely to freeze the entire ChatGPT iPad app, sometimes immediately.
    5. Once frozen, I cannot even switch to a normal non-Remote ChatGPT conversation in the app.
    6. If I background ChatGPT for ~10 seconds and bring it back, Face ID appears and the app then closes/terminates with no visible error.

    Important A/B control:

    • While the iPad is frozen on that long Remote thread, I can open the exact same Remote thread on my iPhone 15 Pro Max and continue using it normally.
    • If I create a brand-new chat inside the exact same Codex project, the iPad can use that new thread without crashing.

    This makes the failure appear strongly correlated with individual thread length/history/state rather than the project, Mac host, network, or Remote pairing itself.

    It may be the same underlying large-thread resume/history reconstruction problem described in this issue, but on iPad the symptom is severe enough to freeze the entire ChatGPT app and then terminate it on foreground restore.

    Happy to provide iPad model, iPadOS version, ChatGPT app version, or any diagnostics/crash logs if useful.

  2. mrcoinzzz commented on Aug 17, 2026

    @mrcoinzzz

    Follow-up after testing with a fresh thread this morning:

    Thread size is clearly a strong aggravating factor, but it does not appear to be the sole trigger.

    • A brand-new/fresh Remote thread on the iPad can also occasionally freeze the ChatGPT app, although much less frequently.
    • Long-established threads freeze substantially more often.
    • Very large threads can freeze immediately on selection, sometimes before the thread contents are even displayed.
    • The same Remote threads continue to work normally on my iPhone 15 Pro Max.

    So creating a fresh thread greatly improves reliability on the iPad, but does not eliminate the underlying Remote freeze. The fact that very large threads can trigger the freeze before their contents render may be useful diagnostically - it suggests the large-thread failure can occur during loading/hydration/resume rather than only while rendering an already-loaded conversation.

    This may therefore be an underlying iPad Remote instability that is significantly amplified by large thread history, rather than thread size being the only cause.

  3. mrcoinzzz commented on Aug 18, 2026

    @mrcoinzzz

    Practical workaround update, in case it helps other iPad Remote users while this is being investigated:

    I have now changed my long-running Codex workflows to use fresh threads regularly. For my daily helpdesk workflow, I start a new thread each morning rather than continuing the very large historical thread.

    To preserve continuity, the new thread receives only a small handover/bootstrap prompt. That prompt tells Codex to read a small project file, which in turn points it to the other persistent instructions/resources it needs. Codex can then reconstruct the working context from the project itself rather than requiring the previous conversation history.

    Result on my iPad:

    • Using the old very large Remote threads would reliably freeze the ChatGPT app within moments, sometimes before the thread even displayed, making Remote effectively unusable.
    • Using fresh threads with this lightweight handover/bootstrap approach makes iPad Remote usable again.
    • It does still freeze occasionally, so this is not a fix for the underlying iPad Remote issue, but the failure frequency is dramatically lower.

    For workflows where the durable state already lives in project files, a ticket system, repository, etc., this seems to be a useful temporary workaround: keep Remote threads relatively short and bootstrap each fresh thread from persistent project resources rather than carrying a huge conversation indefinitely.

  4. dbjaredh commented on Sep 4, 2026

    @dbjaredh

    Adding a current reproduction that appears consistent with this root cause and may help establish user impact beyond the original environment.

    Observed 2026-09-03/04 using Codex Desktop with ChatGPT iOS Remote:

    • One specific long-running Codex thread repeatedly becomes stale/frozen on iOS Remote.
    • The exact same thread remains healthy, current, and fully usable in Codex Desktop.
    • Other Codex threads on the same desktop host continue to synchronize/update correctly on the same iPhone.
    • Therefore the failure appears thread-specific, not account-wide, device-wide, host-wide, pairing-related, or general network/WebSocket connectivity.
    • New desktop activity in the affected thread does not reliably propagate to/hydrate on iOS.
    • Reopening the thread on iOS does not restore current state.

    Recovery attempts that did NOT fix the affected thread:

    • force-close/reopen ChatGPT iOS
    • restart Codex Desktop
    • sign out/in
    • disconnect/re-pair Remote Control
    • reconnect/reopen the affected Remote thread

    This is particularly consistent with the distinction described in this issue: the already-loaded Desktop thread can continue without paying the full thread/resume reconstruction cost, while Remote must resume/hydrate the thread and can stall on a sufficiently large history.

    I cannot provide the private thread contents publicly, but the failure is persistent and reproducible on this one thread. If maintainers want targeted diagnostics, useful next data to request would be: Codex Desktop build, ChatGPT iOS build, host OS, sanitized thread/rollout item counts, timestamps for a failed Remote open/resume, and app-server log lines surrounding thread/resume / composing running thread resume response.

    One UX/reliability improvement in addition to the algorithmic fix: if Remote hydration exceeds a bounded threshold, fall back to metadata-only/partial history and allow the user to steer/monitor the active turn rather than leaving the mobile representation permanently stale. Incremental/paginated hydration plus an explicit retry/resume-from-cursor path would also prevent one oversized thread from becoming effectively unusable on mobile.

    Related reproduction was also added to #28480.

  5. skyzer commented on Sep 7, 2026

    @skyzer

    Adding evidence from 2026-09-07 on a newer release. I can use a small session through iOS Remote, but cannot open existing large sessions. This case involves an already-loaded idle thread, rather than an in-progress turn.

    Environment

    • macOS; ChatGPT desktop 26.901.51231 (build 8109)
    • Bundled Codex binary: 0.153.4
    • Logged iOS client: codex_chatgpt_ios_remote, version 1.2026.237
    • Affected rollout: 8,343,263,018 bytes (7.77 GiB), 174,460 records

    Host evidence

    For the same iOS thread/resume request, on September 7 (UTC):

    16:10:21  resume_running_thread: starts reading saved rollout
    16:10:40  Resumed rollout with 174460 items, parse errors: 0
    16:10:40.569644  composing running thread resume response
                     active_turn_present=false
                     active_turn_id=None
                     active_turn_status=None
    

    Approximately 19 seconds is spent reading/parsing before response composition begins. Existing session content remains readable through desktop thread-read tooling. Another iOS-requested session has 225,684 records and an approximately 13 GiB rollout, but I do not have a comparable completed timing for it.

    Source corroboration in 0.153.4

    • Loaded-thread resume path: include_turns = !params.exclude_turns; for legacy threads, either included turns or initialTurnsPage triggers persisted history loading.
    • Response construction: history reconstruction can run after the composition log. Mobile payload redaction runs afterward, so it does not avoid that preceding work.
    • Mobile redaction removes some large response payloads; the 7.77 GiB disk file should not be interpreted as the bytes transmitted to the phone.

    The request parameters were not captured, so this does not distinguish full-history inclusion from initialTurnsPage. I have not profiled CPU or established that the original issue's quadratic behavior is the precise bottleneck here. The final failure stage after parsing—construction, transfer, or iOS rendering—remains unmeasured.

    A metadata-only attachment followed by progressive history loading would let me continue the same existing task. For legacy sessions, paging should also avoid rereading the entire rollout. Useful diagnostics would separate parse/projection/serialization times, response bytes, and iOS decode/render time, plus capture the client's excludeTurns/initialTurnsPage settings.

    No private session contents, thread/request IDs, credentials, or local paths are included. Session files have not been modified or truncated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    app-serverIssues involving app server protocol or interfacesbugSomething isn't workingperformance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions