When Sonarr's UI hangs after a restart, blame the WebSocket cache
I'd just restarted Sonarr to recover from an unrelated issue. The service came back up, API requests responded fine, but the web UI in my browser was completely unresponsive. Tabs sat blank, the activity icon spun forever.
What was happening
curl against /api/v3/system/status returned in milliseconds with the expected JSON. curl against /index.html returned the HTML page. The actual UI just wouldn't render anything live. The Activity / Queue page showed an empty table forever even though I knew the queue had items.
The browser DevTools panel showed the SignalR/WebSocket connection in a permanently "connecting" state. It would attempt the upgrade, sit for 30 seconds, time out, retry. Repeat forever.
What I found
Sonarr uses SignalR (WebSockets) to push queue updates and activity events to the UI. The client-side connection negotiates an upgrade using a token issued by the server. If the server has restarted but the browser still has the old token cached (in its connection-keepalive state, not in cookies), the upgrade fails because the server no longer recognizes the token.
The browser doesn't always realize the connection is fundamentally dead. It just keeps retrying with the same stale state. This isn't unique to Sonarr — any SignalR or long-lived WebSocket app with stateful auth tokens has the same risk after a server restart.
The fix
Hard refresh the page: Cmd-Shift-R on Mac, Ctrl-Shift-R on Windows/Linux. That blows away the cached connection state and forces a fresh negotiation, which the freshly-restarted server is happy to accept.
If hard refresh doesn't do it (some browsers are stubborn about WebSocket reconnection state in long-running tabs), open the same URL in an incognito/private window. Incognito guarantees a clean slate.
What I'd do differently
This is one of those "the tool works but the client doesn't know" failures that nobody documents because the workaround is so trivial once you know it. I now reflexively hard-refresh any web UI I'm using after restarting its backend service. Costs nothing, saves the occasional 15-minute "why is this broken" rabbit hole.
For my own homelab dashboards, the takeaway is to publish a server-side "I just started" timestamp via a small endpoint and have the client poll it. If the client's notion of the server's start time differs from what the server reports, force a page reload. Fixes itself without any user action.