Skip to content

fix(mobile): polyfill the AbortSignal gaps the connection depends on - #310

Merged
AprilNEA merged 3 commits into
masterfrom
xuan/code-462
Jul 29, 2026
Merged

fix(mobile): polyfill the AbortSignal gaps the connection depends on#310
AprilNEA merged 3 commits into
masterfrom
xuan/code-462

Conversation

@AprilNEA

Copy link
Copy Markdown
Member

Fixes CODE-462. The mobile client never reached the network at all — the failure was a TypeError thrown before the first dial, not anything about reachability.

Root cause

React Native installs AbortController/AbortSignal from the abort-controller npm package (react-native/Libraries/Core/setUpXHR.js), a 2019 polyfill predating two parts of the spec this codebase uses: signal.reason and signal.throwIfAborted().

foxts/async-retry opens with options.signal?.throwIfAborted() — the optional chain guards the signal, not the method. ConnectionController passes a signal, so every recovery attempt threw TypeError: undefined is not a function on entry. No HTTP request ever left the app, which is why the symptom was identical from 127.0.0.1, localhost, and a LAN IP, and why the daemon logged nothing.

Verified in the runtime rather than inferred:

RN (before) Node
signal.throwIfAborted undefined function
signal.reason after abort(new Error(...)) undefined the error
AbortSignal.timeout / .any function function

reason is patched alongside, because the two are one feature: throwIfAborted is specified to throw reason, and ConnectionController re-throws signal.reason when a run is superseded — unpatched that throws undefined, which no catch can classify.

Also here

The connection screen now shows why it failed. ConnectionSnapshot carried the error all along and useHostClient dropped it, so both the user and anyone triaging saw only "unable to reach". That gap is what made this bug take a full session to find. It immediately earns its place — the three states are now distinguishable at a glance:

  • dead port → xhr poll error
  • version-mismatched daemon → LinkCodeClient: handshake timed out after 5000ms; daemon unavailable or wire protocol mismatch
  • fixed → connects

A crash in the new-thread sheet, shipped in #306 and undetectable until a connection existed: BottomSheet needs a <Host> like every other @expo/ui view, and red-boxed without one the moment "New thread" was tapped. Its source reads like a plain RN view, which is what I got wrong; apps/mobile/AGENTS.md is corrected. Also given fitToContents, without which SwiftUI presents this short sheet at a near-full-screen detent.

Verification

pnpm check:ci, pnpm test (2043 passed), 3/3 Maestro flows.

The three screens #306 could not verify are now verified against a live daemon: the thread inbox (host subtitle, empty state), the new-thread sheet (agent picker, workspace picker populated from the host), and the terminal list.

React Native ships a 2019 AbortController without `reason` or
`throwIfAborted`, so every retry given a signal threw before dialing
and the client never reached the network (CODE-462).

Surfaces the controller's error on the connection screen too: the cause
was there all along, thrown away one layer above.
@linear-code

linear-code Bot commented Jul 29, 2026

Copy link
Copy Markdown

CODE-462

AprilNEA added 2 commits July 29, 2026 20:26
Master's lint script OOMs at --concurrency=2 and its pi closure manifest
test fails; both arrived with this merge and neither is reachable from
apps/mobile. Verified with --concurrency=auto: 0 errors.
@AprilNEA
AprilNEA merged commit f8b2387 into master Jul 29, 2026
10 checks passed
@AprilNEA
AprilNEA deleted the xuan/code-462 branch July 29, 2026 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant