Skip to content

Cube preview buffers NDJSON and long iOS turns fail with networkConnectionLost #283

Description

@yxlyx

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

  1. The cube ingress path forwards NDJSON chunks promptly (including the initial started event).
  2. Long-running tool calls do not leave the mobile connection completely idle; heartbeat support may be needed if the ingress can stream it.
  3. The iOS client records NSError domain/code and request phase so failures are diagnosable.
  4. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions