Skip to content

iPhone Connect caches truncated startup JavaScript, causing persistent blank screen; native recovery hits 401 #3791

Description

@brsbl

Summary

A user lost access to BB on their iPhone: both the existing Safari tab at https://brsbl.getbb.app/ and the native BB app showed a persistent black/blank screen. A fresh Private-browser tab worked after sign-in, providing a workaround. Clearing website data and signing in again did not restore the native app; recovery attempts also produced HTTP 401, and the user separately reported HTTP 503 screens.

Inspection of the affected Safari tab confirmed that it repeatedly loaded a truncated cached startup JavaScript file, which failed to parse before the app could render. Fetching the complete file and reloading restored app mounting, including on a subsequent normal reload. The native app remained blank until the user deleted and reinstalled it, which they confirmed restored access.

The Safari blank-screen mechanism is established, but why the file became truncated and cacheable remains unknown. The native app's failing state was not captured, so neither the same corruption nor an authentication problem is established as its cause. Access is recovered; preventing recurrence and making recovery reliable remain open.

Versions and environment

  • Incident reported September 16, 2026, approximately 13:12–13:30 America/New_York; affected phone inspected at approximately 14:42–14:45.
  • Phone: iOS 26.6.1, confirmed by Safari’s device inspector. Affected browser: regular iPhone Safari tab. Phone model and native BB app build remain unknown.
  • Host BB CLI: 0.43.2-nightly.34866755923.1, packaged Nightly on macOS 26.5.2 (25F84). Inspecting Mac Safari: 26.5.2. The successful nightly publication run maps this host version to commit d89160eb8c69c1e3ebc2ba2514f1711af8d7c506 (September 14). This is host build provenance, not the unknown native app build.
  • Latest successful repository-recorded Connect deployment before the incident: September 12, commit 3b37d2790d084a47c96eb78267da5d159598f203, Worker version 27889535-f8a2-413d-9868-6ace3330b64c. Deployment logs establish this record; out-of-band deployments cannot be excluded without Cloudflare access.
  • Connect server: https://brsbl.getbb.app/.
  • Source inspected: e6c615b87056ed2a3aedd46b4c01064f6fa132a9; latest comparison with fetched origin/main at c024ceac524882b7bf9fbef41c14c4911919de4c. Relevant relay/cache/session code and static asset serving logic are unchanged. The deployed-gate record has the same inspected relay/cache/encoding code; the reported host nightly has the same inspected host static-serving and tunnel-client code. This is source comparison, not a reproduction of the initiating failure on main. Native source has evolved since the host nightly and must not be used to infer the installed phone version.

Steps to reproduce

Confirmed browser failure and recovery

This reproduces the reporter’s existing damaged cache, not a deterministic way to create it from a clean installation.

  1. Open the existing regular Safari tab at https://brsbl.getbb.app/ on the affected iPhone. The page is black/blank. Adding a query string to the document URL does not recover it.
  2. Attach Mac Safari Web Inspector to that iPhone tab and reload normally.
  3. Observe an empty #root, the startup script loaded from memory cache, and the syntax error below.
  4. Export the network trace locally and compare the cached startup response with fresh responses for /assets/index-BaDU-L7_.js (exact URL and a cache-busted URL with cache: no-store). The cached body is an exact prefix of both complete responses.
  5. After fetching the complete responses and reloading, observe populated app DOM and resumed application requests. Repeat a normal reload: app mounting succeeds and the startup SyntaxError does not return. The reporter’s visual confirmation has not been obtained.

A fresh Private-browser tab also worked after sign-in. No explicit cookie/site-data deletion or new sign-in was needed during the inspected regular-tab recovery.

Native failure and recovery, reporter-observed

  1. The native BB app’s saved connection initially shows a blank page.
  2. Long-press the home-screen BB icon → Device settings → Clear website data. This does not restore access; the reporter subsequently captures HTTP 401.
  3. Generate a fresh code on the host with bb connect machine-code. On the phone, use Device settings → Servers → long-press server → Sign in again, then redeem the code.
  4. The code lookup confirms consumed: true; the reporter still sees the same 401 screen. The phone’s post-pairing cookie installation/transmission is unverified.
  5. After the browser recovery, the reporter confirms the native app is still blank.
  6. Delete and reinstall the native BB app: the reporter confirms this fixes it. Whether the same build was reinstalled and whether fresh pairing/sign-in was required have not been confirmed. The old native state is no longer available for inspection.

Expected vs actual

Expected: complete startup assets execute and BB loads; failed transfers should not leave a persistently unusable cached app. Native recovery should restore a usable session.

Actual browser console:

SyntaxError: Unexpected end of script
/assets/index-BaDU-L7_.js:26

Native error captured during failed recovery (before the successful reinstall):

The server answered with an error
HTTP 401
Try again
Device settings

Reporter-provided native HTTP 401 screenshot

HTTP 503 screens were also reported. Their exact timing, client, and response body remain unknown.

Evidence

Confirmed truncated cache entry

Measurement Cached startup response Fresh response
Decoded body bytes 832,191 1,011,640
SHA-256 5fea59a405972cb2f24f7b61da9ee1cb0c9b357493eb5110e4103cec241e2db1 85a209186bb8f3d0d1dac1b6d4ae95f2342436dcd0a2d55bf68e39a2d34dbe74

The cached body is an exact prefix of the complete file, missing 179,449 bytes, and ends mid-assignment:

...t[61]=se,t[62]=I,t[63]=n,t[64]=

The failing response records HTTP 200 and:

Cache-Control: public, max-age=31536000, immutable
Content-Encoding: br
Vary: Accept-Encoding, Origin
Date: Wed, 16 Sep 2026 00:58:45 GMT
x-bb-cache: miss
cf-ray: a3bbec720a237c96-EWR

Causal mechanism: truncated immutable script cached on the phone → reload reuses it → parsing fails before React mounts → empty black page. Changing the document query leaves the hashed script URL unchanged. Private browsing avoids the damaged cache. Authentication does not explain this reproduced browser failure.

Both fresh phone fetches returned the same complete bytes: the exact-URL fetch arrived with zstd, and the cache-busted fetch with Brotli. This does not establish a compression-specific defect. The startup error happens before React and its error boundary are installed, explaining the lack of an in-app crash panel.

Raw HAR headers were not published because they can contain credentials. The temporary HAR was removed after extracting and verifying the measurements. The malformed bytes remain reproducible from the original complete asset: take its first 832,191 bytes; that prefix reproduces the captured bad SHA-256 exactly. This reconstructs the payload, not the original cache-writing failure.

Healthy origin representations

Fresh HTTP reads of the same asset from the host origin http://127.0.0.1:38886 all completed normally. Each decoded to 1,011,640 bytes and the good SHA-256 above:

Requested representation Wire bytes Origin Content-Length Decoded hash
Identity 1,011,640 1,011,640 Good hash above
Brotli 237,668 237,668 Good hash above
Gzip 300,285 300,285 Good hash above

The host static handler reads the asset into memory and supplies the compressed file length for precompressed responses. These checks rule out a persistently malformed current origin artifact, not a transient failure during the original transfer. The captured failed response headers contained no Content-Length.

Lead for the initiating truncation event

The cached response predates daytime troubleshooting. Host server.69.log shows a tunnel interruption about 68 seconds after its response timestamp:

2026-09-16T00:59:52.831Z tunnel heartbeat missed; reconnecting
2026-09-16T00:59:52.833Z tunnel closed (code 1006); reconnecting in 1000ms
2026-09-16T00:59:54.233Z tunnel connected

The wider 00:53–01:04 UTC host log window also contains missed tunnel heartbeats at 00:53:08.093, 00:54:10.654, and 01:00:54.236. At 00:59:04.922 an npm lookup was aborted; at 00:59:05.534 a host lookup failed with host_unavailable. These support broader instability around the download, without identifying the failing layer. A bounded macOS power-log check found no Sleep/Wake transition in this window; this does not exclude network loss or scheduling stalls.

Heartbeat detection runs every 20 seconds with a 60-second acknowledgment deadline (source). Therefore the logged disconnect time is detection time, not a precise timestamp for the first lost byte.

Use the original response timestamp and ray ID to inspect edge logs. x-bb-cache: miss is a stored response header, not proof that every later delivery bypassed all caches; another Cloudflare cache layer can replay it. Host logs do not record byte counts for this asset request: detailed response-size logging is limited to initial thread-load APIs. Cloudflare request logs/runtime traces were unavailable with the investigation’s credentials.

Transport and cache source audit: leads, not a proven initiating defect

  • The host tunnel client sends body-end after consuming the origin response; an origin read error goes through close-stream. The edge closes normally on body-end, while disconnect handling requests a stream abort. Source inspection did not prove that a disconnect is intentionally converted into successful EOF.
  • The edge relay uses an ordinary TransformStream, with no expected-length accounting before accepting body-end; that protocol message carries no byte count or digest. Cloudflare documents that manually set lengths are ignored for arbitrary streaming bodies. Its FixedLengthStream can enforce exact byte counts. This identifies a potential integrity-check improvement, not proof that its absence caused this incident; compression/transcoding boundaries must be handled correctly.
  • Asset caching clones and streams cacheable successful responses into cache.put while serving the visitor. No application-level asset checksum is checked. The observed incomplete response nevertheless being reusable is proven; which layer treated it as complete is not.
  • Do not blindly change the response encoding mode: PR #915 established that the relay/cache-hit paths need manual encoding while the cache-miss path is automatically decoded/re-encoded. Its raw-gzip corruption differs from this exact plaintext-prefix truncation.
  • Existing Node-level stream replacement coverage expects a pending body read to reject. Existing workerd encoding tests cover complete compressed bodies and cache hits/misses. Neither demonstrates interrupted, partially delivered compressed assets through edge caching and actual Safari. These tests were read, not run in this diagnosis.

Separate native recovery defect

A further startup-detection limitation: native onLoadEnd marks the page ready without proving that the app JavaScript mounted. This can fail to surface a bundle startup error, but the native bundle error was not captured here.

Recommended recovery direction: await data clearing, reinstall a fresh Connect session before reload, and route page-level 401 through session recovery. Cache clearing must also work without a mounted WebView. Fresh pairing changes the connection identity and should already mint/install a cookie, so its reported failure is not explained solely by the reset defect. Still needed: the native page’s failing URL/status and cookie-presence/validation result, without disclosing token values, plus the native app build.

HTTP 503 recovery limitation

Connect emits HTTP 503 when no open host tunnel is available. Its HTML error page refreshes every ten seconds, but the native app replaces main-frame HTTP errors with its generic panel and unmounts the page, preventing that HTML recovery from running. This is a source-level recovery limitation; the reporter’s uncaptured 503 responses cannot yet be attributed to this specific path.

Originating BB thread (owner access required).

What was ruled out / remains open

  • Fresh public startup assets were complete; the damaged phone copy differs from them. Earlier healthy host/network checks did not examine that cache.
  • The browser blank screen is reproduced and explained. The original event that allowed an incomplete response into cache is not yet established.
  • Native cache corruption has not been verified. Reinstalling recovered access; the reasons for its earlier blank screen, failed re-pair recovery, and reported 503s remain unproven. A changed app build or session during reinstall is a possible confound.
  • Inspection limitation: after reconnecting/unlocking the iPhone, Mac Safari exposed its Safari tabs but no native BB WebView. Current BB source gates WebView debugging on __DEV__. The installed app build remains unknown; this explains the expected release-build limitation but does not diagnose its blank screen. macOS Console did not list the phone as an available log source.
  • Earlier clean source-app checks in Chrome for Testing and desktop Safari did not reproduce the damaged phone state. No native simulator was available.
  • bb Connect: a plugin serving a sandboxed document cannot serve that document's own files #3617 concerns sandboxed plugin subresources; this failure is the top-level startup bundle. Mobile Remote Access Template - UI/UX Hidden Due to Responsiveness Issues #1603 concerns older Safari parsing compatibility; here the body is demonstrably truncated.
  • No product code changes, PR, deployment, or local tests were made.

Remaining investigation and verification

Available incident evidence, source/history, origin representations, publication/deployment records, and bounded host/power logs have been examined. No production restart, cache purge, configuration change, code change, or deliberate connection interruption was performed.

To establish the initiating defect, the next useful work is:

  1. With authorized Cloudflare access, correlate ray a3bbec720a237c96-EWR and the original timestamp with request outcomes, stream errors, cache status and byte counts if those fields were retained. Historical metadata may be insufficient to recover the original cause.
  2. In an isolated environment, interrupt host/tunnel and visitor connections partway through a compressed immutable asset; verify that the response errors and neither edge nor Safari reuses a partial success. Cover cache miss/hit and slow-reader backpressure. This fault-injection reproduction was not performed; local tests are prohibited in this workspace and no remote test change was created for this diagnosis.
  3. Verify recovery with a deliberately damaged cached startup asset: startup failure should be visible, recovery must fetch valid assets, and native data clearing must renew the session rather than leaving 401. Verify this in real iOS Safari and the native WebView.

The user's reinstall removed the old native state. Without a new occurrence or reproducible fault injection, identical browser/native truncation cannot be proven retrospectively.

Suggested priority and effort

High: one user lost browser and native access until fresh asset retrieval or reinstall. Access is now recovered; the permanent prevention/recovery fix is still open. Breadth is unknown; no server data loss was reported.

AGENT GENERATED

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingconnectBuilt-in plugin: connectmobileMobile web: iOS Safari, touch, layoutremoteMulti-device, LAN/tailnet access, notifications

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions