Codex Desktop SSH handoff fails for conversations larger than 16 MiB because rollout is sent as one WebSocket message

Open 💬 3 comments Opened Jul 12, 2026 by fkruger

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

26.707.41301

What subscription do you have?

Pro

What platform is your computer?

Linux 6.8.0-134-generic x86_64 x86_64

What issue are you seeing?

Summary

A cross-host handoff from Codex Desktop on macOS to a connected Ubuntu SSH
host consistently fails during transfer-host-artifacts when the task has a
large conversation history.

The rollout file is approximately 24.7 MB. Codex attempts to transfer it as one
WebSocket message whose serialized size is 32,936,861 bytes. The remote
app-server rejects it because its per-message limit is 16,777,216 bytes.

This prevents preserving and handing off long-running conversations.

Environment

  • Codex Desktop client build: 26.707.41301
  • Local codex-cli: 0.144.0-alpha.4
  • Local OS: macOS
  • Remote codex-cli: 0.144.1
  • Remote platform: Ubuntu x86_64
  • Connection type: Codex Desktop SSH host
  • Source project: local Git checkout
  • Destination: matching saved project on the SSH host

Steps to reproduce

  1. Create or use a Codex task with a sufficiently large conversation history.
  2. Ensure its rollout JSONL is larger than 16 MiB.
  3. Configure a matching Git project on a connected SSH host.
  4. In a separate coordinator task, hand off the large task to that SSH host.
  5. Wait for the cross-host handoff.

Expected result

The conversation and associated Git state are transferred to the SSH host, and
the task switches to the remote project.

Actual result

prepare-host-transfer completes, but transfer-host-artifacts fails:

Failed to continue on another host:
App-server connection closed before process exited

The remote app-server log contains:

websocket receive error:
Space limit exceeded: Message too long: 32936861 > 16777216

The SSH proxy subsequently reports:

Error: failed to relay data between stdio and socket

Caused by:
failed to copy data from stdin to socket
Broken pipe (os error 32)

Relevant sizes

  • Source rollout JSONL: 24,702,507 bytes
  • Source rollout lines: 18,217
  • Serialized WebSocket message: 32,936,861 bytes
  • Remote WebSocket limit: 16,777,216 bytes

Reproducibility

Consistent across repeated attempts.

The following did not help:

  • Rebooting the SSH host
  • Disabling and re-enabling the SSH connector
  • Restarting the remote app-server
  • Staging the task in a local Codex worktree before remote handoff
  • Confirming both repositories are clean and at the same commit

Probable cause

The handoff creates a destination rollout.jsonl using a subprocess similar to:

cat > <handoff-directory>/rollout.jsonl

It appears to send the complete rollout to the remote subprocess in one
WebSocket/process-input message. Conversations whose serialized transfer
payload exceeds the app-server's 16 MiB per-message limit therefore cannot be
handed off.

Requested fix

Please stream or chunk handoff artifacts into messages substantially below the
WebSocket limit, rather than sending the complete rollout in one message.

For example:

  • Send process input in 4–8 MiB chunks, or
  • Transfer the file using a streaming filesystem operation or SSH file transfer.

It would also help if the UI surfaced the underlying size-limit error instead of
only reporting that the app-server connection closed.

Impact

There is currently no supported workaround that both:

  1. Completes the SSH handoff, and
  2. Preserves the complete conversation history.

Summarizing or truncating the task would lose the conversation state that the
handoff feature is intended to preserve.

What steps can reproduce the bug?

Steps to reproduce

  1. Create or use a Codex task with a sufficiently large conversation history.
  2. Ensure its rollout JSONL is larger than 16 MiB.
  3. Configure a matching Git project on a connected SSH host.
  4. In a separate coordinator task, hand off the large task to that SSH host.
  5. Wait for the cross-host handoff.

What is the expected behavior?

Please stream or chunk handoff artifacts into messages substantially below the
WebSocket limit, rather than sending the complete rollout in one message.

Additional information

_No response_

View original on GitHub ↗

3 Comments

sam-ulrich1 · 1 month ago

Please fix

bbingz · 1 month ago

Analysis (community)

Residual analysis for #32512 (Desktop SSH handoff fails for conversations larger than ~16 MiB because the rollout is sent as one WebSocket message) under docs/contributing.md — invitation-only. No unsolicited PR.

Root-cause hypothesis

This is a process/writeStdin artifact transfer failure, not primarily a large thread/resume response. Desktop sent the whole rollout as one base64 delta (~24.7 MB → ~32.9 MB wire); the remote app-server Unix WebSocket acceptor still uses default ~16 MiB Tungstenite limits and fatal-closes on receive overflow — even though the protocol already supports chunked process/writeStdin deltas.

Verified at HEAD 4c43465133:

1. Protocol already has the right shape.
codex-rs/app-server-protocol/src/protocol/v2/process.rs — process/writeStdin carries optional delta_base64 + close_stdin.
process_exec_processor.rs decodes one supplied delta and writes it to the process. Multiple smaller deltas are valid; a single huge delta is what blows the frame.

2. Issue body pins the exact path.
#32512 reports a ~24,702,507-byte rollout becoming one ~32,936,861-byte WebSocket message, rejected at 16,777,216 bytes on the remote app-server during transfer-host-artifacts — classic ~4/3 base64 expansion of a single delta.

3. Unix acceptor keeps default limits.
codex-rs/app-server-transport/src/transport/unix_socket.rs upgrades with accept_async under default Tungstenite frame/message limits. A receive-limit error breaks the inbound loop (transport/websocket.rs); the connection then emits ConnectionClosed. By contrast, an in-tree remote client config raises frame/message limits to 128 MiB (app-server-client remote config) — that raise is not applied to the Unix-socket server acceptor.

4. Scope honesty (MIXED).

  • OSS residual: default 16 MiB receiver + fatal close on the Unix control path; chunked deltas already supported server-side.
  • Closed residual: the handoff sender that packs the entire rollout into one delta (Desktop). Primary fix is client-side chunking (e.g. 4–8 MiB deltas); OSS can still add explicit acceptor limits + better diagnostics as defense-in-depth.
  • Earlier framing that blamed only large outbound thread/resume was overbroad for this issue; #32512’s bytes are stdin artifact transfer.

Ranked fix outline:

  1. Desktop (primary): stream process/writeStdin as multiple delta_base64 chunks under the wire cap; final chunk + close_stdin.
  2. OSS defense-in-depth: set explicit max frame/message on the Unix WebSocket acceptor (document the value); surface a typed “payload too large / use chunked writeStdin” error instead of silent fatal close when possible.
  3. Align acceptor limits with the 128 MiB remote-client policy or document why server stays lower and force chunking.
  4. Regression: synthetic multi-delta writeStdin of >16 MiB total succeeds; single >16 MiB delta fails with actionable error.

Fail-first tests on the OSS side can mock multi-delta vs single-delta without needing full Desktop. Happy to help with acceptor config + error string. No unsolicited PR.

shleder · 6 days ago

The 16,777,216-byte per-message limit vs a 32.9 MB serialized rollout is a clean mismatch — the rollout is being shipped as a single WebSocket frame instead of streamed/chunked, so any conversation past ~16 MiB of JSONL is un-handoffable.

Before a handoff, it's worth knowing the rollout's actual size and record composition, because the fix (chunked transfer or a size gate with a clear error) depends on whether the bulk is transcript or embedded payloads. A 24.7 MB rollout that's mostly inline images is a different problem than one that's genuinely long conversation.

Full disclosure: I maintain codex-rescue (https://github.com/shleder/codex-rescue), which handles >16 MiB rollouts via bounded chunk streaming specifically because this threshold comes up in practice. codex-rescue storage will report the rollout size and payload breakdown read-only, so you can tell whether the handoff is blocked by transcript length or by embedded media. Useful for deciding whether the client should chunk or reject-with-reason.