Repository navigation
Cube preview buffers NDJSON and long iOS turns fail with networkConnectionLost #283
Description
Summary
Long cube turns from the iOS app can fail with:
Transport error: The network connection was lost.
The sandbox and graff serve remain healthy. The external Daytona preview route appears to buffer the complete application/x-ndjson response instead of forwarding events as they are emitted. This leaves a physical iPhone with an idle HTTP request for the full model/tool duration and eventually produces NSURLErrorNetworkConnectionLost on longer work.
Production observation
Environment:
- Graff iOS 0.1, physical iPhone 16 Pro Max, iOS 26.5.2
- Cube running
graff serve0.0.213 - Session workload: multi-PR code review with tool calls
The account-synced transcript persisted four failures with the exact localized error above. At diagnosis time:
- the sandbox was
started - the
graff serveprocess and port 8787 listener were alive /healthzreturned 200 immediately and reported no stuck sessions- there was no OOM event or Graff crash report
- a short turn through the same preview URL completed successfully from both Python and the iOS Simulator
Buffering reproduction
A diagnostic turn was sent through the sandbox preview URL with a tool call that ran sleep 40.
Response headers arrived as HTTP 200 with Transfer-Encoding: chunked, but the first NDJSON event did not reach the client until 50.1 seconds, at which point all 58 events (started, reasoning, tool_call, tool_result, text, and turn) arrived together with zero measured gaps.
started and the early model events should be delivered immediately. Buffering the whole response defeats the streaming protocol and exposes mobile clients to idle-connection termination.
Relevant client behavior
GraffServeClient.streamTurnusesURLSession.shared.bytes(for:):apps/ios/Graff/Sources/GraffServeClient.swiftChatView.runTurncatches the error and persists onlyerror.localizedDescription:apps/ios/Graff/Sources/ChatView.swift- The client has no safe reconnect/resume mechanism for an interrupted non-idempotent turn.
Expected
- The cube ingress path forwards NDJSON chunks promptly (including the initial
startedevent). - Long-running tool calls do not leave the mobile connection completely idle; heartbeat support may be needed if the ingress can stream it.
- The iOS client records
NSErrordomain/code and request phase so failures are diagnosable. - Recovery must avoid blindly replaying a possibly accepted tool-running turn; use an idempotency key or a server-side attach/resume mechanism.
Acceptance check
A turn that emits an initial event and then spends 40+ seconds in a tool should deliver the initial event immediately through the public cube URL and finish on a physical iPhone without NSURLErrorNetworkConnectionLost.
Activity
justrach commentedon Sep 29, 2026
Closing as not planned: the iOS app is not being continued.