Originally created by: imshaikot
After a sleep, or whenever the daemon went away for a while, the panel could sit on Reconnecting indefinitely while a working socket to the daemon was open underneath.
Cause. A worker woken by the reconnect alarm calls connectDaemon() twice at once, from the top of background.ts and from the alarm listener. The live-socket check ran before two storage reads, so both calls dialed. The daemon keeps one link per install and closed the older socket. That socket's onclose still marked the link down and scheduled a retry. The retry called dial() directly, so it replaced the live socket, whose close scheduled the next retry. The loop never ended: backoff capped at 15–30 s, and each cycle's last write was connected: false. Each replacement also disposed the daemon's agent session for that browser, so a run in progress was killed.
~/.browsentic/daemon.log on my machine shows two episodes on 25 Sept: 47 minutes with 127 replacements, and 20 minutes with 57. Each starts with two sockets connecting in the same millisecond.
Fix (src/lib/bridge/socket.ts only)
connectDaemon() is single-flight and checks for a live socket again after its storage reads.connectDaemon() instead of dial().setState writes are queued one at a time, as in monitor.ts, so the agent and skill pushes that follow a welcome can't write back a stale connected.Test. socket.test.ts runs the real socket.ts against a real daemon in the test sandbox. It revives a paired worker, calls connectDaemon() twice and waits past the first retry. It expects one connection and the link online. Before the fix it fails with 3 connections, and it passes 3/3 after. It takes about 1.7 s. yarn check (1743 tests), yarn build and yarn build:firefox pass.
Not verified in a real Chrome through an actual sleep.
No protocol change. The extension alone needs the update.
Merged with main after [#37]. Scheduled tasks added a native-wake step to giveUp, and its retry after a wake called dial() directly, the same bypass this PR closes. The merge keeps the wake and sends that retry through connectDaemon() as well. yarn check (1833 tests) and yarn build pass on the merge.
🤖 Generated with Claude Code
Ticket changed by: imshaikot