Summary
We observed a UX regression after upgrading from Bandit 1.8.0 to 1.10.4: when one Phoenix LiveView channel on a multi-LV page receives a Phoenix.PubSub "disconnect" broadcast and exits, the client's LiveSocket takes 4-5 seconds before completing its reconnect cycle (and ultimately falling back to window.location.reload()). With 1.8.0, the same code path completes in milliseconds and the disconnect indicator never fires. Downgrading the bandit dep — without changing anything else — restored fast behaviour.
I have not bisected the intermediate versions yet. Reporting now in case it rings a bell, or in case someone else can reproduce more cheaply against your test suite.
Environment
- bandit: 1.10.4 (bad), 1.8.0 (good)
- phoenix: 1.8.5
- phoenix_live_view: 1.1.28
- websock_adapter: 0.5.9
- thousand_island: 1.4.3
- elixir: 1.18.3
- OTP: 27
- adapter config: standard
Bandit.PhoenixAdapter via phx.gen.auth-style endpoint, no custom transport options
- platform: darwin (also reproduces in Linux production builds — same range)
Symptom
A Phoenix endpoint serves a page that contains TWO LiveView channels on the same socket:
- a layout-level LV (e.g. a sidebar rendered via
<.live_render>)
- the page LV
Both LVs are in the same live_session and subscribe to the same live_socket_id topic via Phoenix.PubSub — this is the standard Phoenix-gen.auth pattern for "log out everywhere" disconnect handling.
When one of those LV channels handles a %Phoenix.Socket.Broadcast{event: "disconnect"} message and exits with {:stop, {:shutdown, :disconnected}, …}, the LiveSocket client (LV.js) sees the channel close and tries to rejoin.
- On bandit 1.8.0: rejoin is attempted within ~50–200 ms, fails (the underlying user session has been deleted), and LV.js falls back to a clean
window.location.reload(). Total time-to-navigation: ~300–500 ms. The user does not perceive a disconnect.
- On bandit 1.10.4: there is a 4-5 s gap before the same fallback fires. During that gap, the standard Phoenix disconnect indicator (
#client-error / #server-error flash, default disconnectedTimeout: 1000) appears with "Attempting to reconnect" copy. Visible UX regression.
The symptoms strongly suggest the WebSocket close handshake or the per-frame timing on the wire changed between 1.8.0 and 1.10.4, in a way that interacts with LiveSocket's reconnect-backoff scheduler. I have not been able to confirm whether the slowdown is in:
- the WebSocket close-frame handshake itself (
thousand_island socket teardown timing)
- bandit's HTTP/1.1 keep-alive vs. close behaviour after WebSocket termination
- some new buffering / flush behaviour that delays the close frame from reaching the client
Reproduction (sketch)
We don't yet have an isolated minimal reproduction. The conditions are:
- Phoenix endpoint behind
Bandit.PhoenixAdapter.
- A page with two
<.live_render>'d LiveViews in the same live_session (one in the layout, one as the page). Both subscribe to the user's live_socket_id topic at mount.
- Trigger: from inside one LV's
handle_event/3, broadcast a disconnect message on the user's live_socket_id topic via Phoenix.PubSub.broadcast/3 (or broadcast_from/4).
- Observe: the OTHER LV's channel terminates as expected, but the LV.js client's reconnect cycle stretches to ~4 seconds before completing.
If a minimal reproduction would help, I'm happy to put one together — say so and I'll prepare it.
Workaround we shipped
Pinned to bandit, "1.8.0" in mix.exs. Comment in our deps:
# Pinned to 1.8.0 — the 1.8.0 → 1.10.x update introduced a
# WebSocket close/reconnect regression that surfaces as a 4-5 second
# freeze + visible "Attempting to reconnect" overlay whenever a server-
# issued LV `redirect/2` crosses live_session boundaries (e.g.
# `/user/account` → `/user/log_in` after a session-invalidating
# self-change like password / MFA / email-confirm). Bisected
# 2026-05-10 against this manual test path; 1.8.0 is clean,
# 1.10.4 reproduces. Re-test before bumping; consider pinning to
# a confirmed-good 1.9.x in the interim if a subsequent release
# fixes it.
{:bandit, "1.8.0"},
What I'd find useful
- A second pair of eyes on whether the symptom maps to anything that changed in the
1.9.x line (websock close handling? thousand_island integration?).
- A hint on which 1.9 release I should bisect against next (if anyone has a guess based on changelog).
- Confirmation that this isn't user error on our side (e.g. some endpoint config I should be setting).
Happy to run additional diagnostics on our setup — packet captures, telemetry traces, anything that helps.
Thank you for bandit — 1.8.0 has been rock solid for us for months.
Summary
We observed a UX regression after upgrading from Bandit
1.8.0to1.10.4: when one Phoenix LiveView channel on a multi-LV page receives aPhoenix.PubSub"disconnect" broadcast and exits, the client'sLiveSockettakes 4-5 seconds before completing its reconnect cycle (and ultimately falling back towindow.location.reload()). With1.8.0, the same code path completes in milliseconds and the disconnect indicator never fires. Downgrading the bandit dep — without changing anything else — restored fast behaviour.I have not bisected the intermediate versions yet. Reporting now in case it rings a bell, or in case someone else can reproduce more cheaply against your test suite.
Environment
Bandit.PhoenixAdapterviaphx.gen.auth-style endpoint, no custom transport optionsSymptom
A Phoenix endpoint serves a page that contains TWO LiveView channels on the same socket:
<.live_render>)Both LVs are in the same
live_sessionand subscribe to the samelive_socket_idtopic viaPhoenix.PubSub— this is the standard Phoenix-gen.auth pattern for "log out everywhere" disconnect handling.When one of those LV channels handles a
%Phoenix.Socket.Broadcast{event: "disconnect"}message and exits with{:stop, {:shutdown, :disconnected}, …}, the LiveSocket client (LV.js) sees the channel close and tries to rejoin.window.location.reload(). Total time-to-navigation: ~300–500 ms. The user does not perceive a disconnect.#client-error/#server-errorflash, defaultdisconnectedTimeout: 1000) appears with "Attempting to reconnect" copy. Visible UX regression.The symptoms strongly suggest the WebSocket close handshake or the per-frame timing on the wire changed between
1.8.0and1.10.4, in a way that interacts withLiveSocket's reconnect-backoff scheduler. I have not been able to confirm whether the slowdown is in:thousand_islandsocket teardown timing)Reproduction (sketch)
We don't yet have an isolated minimal reproduction. The conditions are:
Bandit.PhoenixAdapter.<.live_render>'d LiveViews in the samelive_session(one in the layout, one as the page). Both subscribe to the user'slive_socket_idtopic at mount.handle_event/3, broadcast adisconnectmessage on the user'slive_socket_idtopic viaPhoenix.PubSub.broadcast/3(orbroadcast_from/4).If a minimal reproduction would help, I'm happy to put one together — say so and I'll prepare it.
Workaround we shipped
Pinned to
bandit, "1.8.0"inmix.exs. Comment in our deps:What I'd find useful
1.9.xline (websock close handling? thousand_island integration?).Happy to run additional diagnostics on our setup — packet captures, telemetry traces, anything that helps.
Thank you for bandit —
1.8.0has been rock solid for us for months.