Skip to content

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

Closed as not planned

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.

Activity

  1. justrach commented on Sep 29, 2026

    @justrach
    Owner

    Closing as not planned: the iOS app is not being continued.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingiOSiOS apppriority:p2Medium priority

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions