Environment
- Phoenix 1.8.5 (phoenix.js), LiveView 1.1.28
- Chrome 138, observed in production behind enterprise security proxies (e.g. Zscaler-class appliances)
Summary
Socket.connectWithFallback (assets/js/phoenix/socket.js) can only fall back to the fallback transport in two ways: the fallback timer fires before any successful open, or an error arrives before any successful open (if(primaryTransport && !established)). The onOpen handler sets established = true permanently.
A websocket that opens successfully and then dies moments later — which is exactly how several enterprise middleboxes behave (the 101 upgrade succeeds, then the appliance kills the tunnel ~100-500ms in) — escapes both paths. After the first open, established is latched, so every subsequent error takes the normal reconnect path with reset backoff, the websocket opens again, dies again, and the client flaps forever. Longpoll is never tried.
Reproduction
Adversarial TCP proxy in front of a Phoenix app that allows the websocket upgrade to complete, then closes the connection 200ms after open. With longPollFallbackMs set, the client falls back only when the kill races ahead of the open event; when the kill lands after open, the client flaps indefinitely (verified with a scripted browser: continuous open/close cycles, zero longpoll attempts, backoff never grows because each open resets it).
Expected
N consecutive open-then-die cycles within a window should count as primary-transport failure and trigger the same fallback as never having connected — the fallback exists precisely for networks that can't sustain websockets.
Real-world impact
We traced a large customer's users stuck in this flap on their corporate network (appliance kills established websockets). Because fallback never engaged, they had no working transport path from the fallback logic at all.
We have a reproduction harness (node middlebox proxy + Playwright driver) and are happy to share it or test a patch.
Filed on behalf of Rupert Deese by Claude, the AI engineering assistant at Gearflow, which found and verified this during a production transport investigation.
Environment
Summary
Socket.connectWithFallback(assets/js/phoenix/socket.js) can only fall back to the fallback transport in two ways: the fallback timer fires before any successful open, or an error arrives before any successful open (if(primaryTransport && !established)). TheonOpenhandler setsestablished = truepermanently.A websocket that opens successfully and then dies moments later — which is exactly how several enterprise middleboxes behave (the 101 upgrade succeeds, then the appliance kills the tunnel ~100-500ms in) — escapes both paths. After the first open,
establishedis latched, so every subsequent error takes the normal reconnect path with reset backoff, the websocket opens again, dies again, and the client flaps forever. Longpoll is never tried.Reproduction
Adversarial TCP proxy in front of a Phoenix app that allows the websocket upgrade to complete, then closes the connection 200ms after open. With
longPollFallbackMsset, the client falls back only when the kill races ahead of the open event; when the kill lands after open, the client flaps indefinitely (verified with a scripted browser: continuous open/close cycles, zero longpoll attempts, backoff never grows because each open resets it).Expected
N consecutive open-then-die cycles within a window should count as primary-transport failure and trigger the same fallback as never having connected — the fallback exists precisely for networks that can't sustain websockets.
Real-world impact
We traced a large customer's users stuck in this flap on their corporate network (appliance kills established websockets). Because fallback never engaged, they had no working transport path from the fallback logic at all.
We have a reproduction harness (node middlebox proxy + Playwright driver) and are happy to share it or test a patch.
Filed on behalf of Rupert Deese by Claude, the AI engineering assistant at Gearflow, which found and verified this during a production transport investigation.