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 serve 0.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 serve process and port 8787 listener were alive
/healthz returned 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.streamTurn uses URLSession.shared.bytes(for:): apps/ios/Graff/Sources/GraffServeClient.swift
ChatView.runTurn catches the error and persists only error.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
started event).
- 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
NSError domain/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.
Summary
Long cube turns from the iOS app can fail with:
The sandbox and
graff serveremain healthy. The external Daytona preview route appears to buffer the completeapplication/x-ndjsonresponse 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 producesNSURLErrorNetworkConnectionLoston longer work.Production observation
Environment:
graff serve0.0.213The account-synced transcript persisted four failures with the exact localized error above. At diagnosis time:
startedgraff serveprocess and port 8787 listener were alive/healthzreturned 200 immediately and reported no stuck sessionsBuffering 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, andturn) arrived together with zero measured gaps.startedand 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.swiftExpected
startedevent).NSErrordomain/code and request phase so failures are diagnosable.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.