Description
The daemon's WebSocket connection to relay.factory.ai drops every few minutes with code 1006 ("Connection ended" -- abnormal closure, no WebSocket close frame sent). The daemon reconnects within ~1 second, but during that reconnect window, web sessions become "session unavailable" because the relay's orphan grace period is shorter than the reconnect time.
Root Cause
The daemon does not send WebSocket-level ping/pong frames and does not enable TCP keepalive (socket.setKeepAlive()) on the relay WebSocket connection. Intermediate infrastructure (home router NAT, ISP gateways, load balancers) drops the idle TCP connection after their timeout window (typically 60-300 seconds).
This matches the classic WebSocket timeout pattern described at websocket.org/guides/troubleshooting/timeout: connections die silently without FIN/RST when intermediate devices drop idle NAT mappings.
Evidence (from ~/.factory/logs/droid-log-single.log)
Drops consistently reoccur, with intervals of ~2-14 minutes between reconnects:
[2026-07-28T00:01:38.605Z] WARN: [Relay] Disconnected | Context: {"code":1006,"reason":"Connection ended"}
[2026-07-28T00:01:39.618Z] INFO: [Relay] Connected
[2026-07-28T00:08:33.976Z] WARN: [Relay] Disconnected | Context: {"code":1006,"reason":"Connection ended"}
[2026-07-28T00:08:34.942Z] INFO: [Relay] Connected
[2026-07-28T00:22:25.158Z] WARN: [Relay] Disconnected | Context: {"code":1006,"reason":"Connection ended"}
[2026-07-28T00:22:26.231Z] INFO: [Relay] Connected
The reconnection is fast (~1 second), but the relay's orphan grace window appears to be ~2 seconds, which is too tight for this reconnect frequency.
Environment
- OS: Windows 10 (build 26200), BYOM
- Factory: latest daemon (auto-updating)
- Network: behind consumer NAT (192.168.1.x), Tailscale installed
- Windows TCP keepalive registry settings were tuned (KeepAliveTime=30000ms) but had no effect -- Node.js must explicitly call
setKeepAlive() for these to take effect
Proposed Fix
In the daemon's relay WebSocket connection code, add one or both of:
-
TCP keepalive (minimal, catches dead connections after ~30s):
socket.setKeepAlive(true, 30000); // enable keepalive, 30s idle before first probe
-
WebSocket-level ping/pong (proper fix, keeps intermediate proxies alive):
setInterval(() => ws.ping(), 25000); // 25s beats all common proxy defaults
Both are one-liners. The net-keepalive npm package provides portable TCP_KEEPIDLE/TCP_KEEPINTVL for finer control if needed.
Impact
Every web-initiated session on this BYOM computer breaks after the first message exchange. Local CLI sessions work fine because they go through the daemon's local transport, not the relay WebSocket.
Description
The daemon's WebSocket connection to
relay.factory.aidrops every few minutes with code 1006 ("Connection ended" -- abnormal closure, no WebSocket close frame sent). The daemon reconnects within ~1 second, but during that reconnect window, web sessions become "session unavailable" because the relay's orphan grace period is shorter than the reconnect time.Root Cause
The daemon does not send WebSocket-level ping/pong frames and does not enable TCP keepalive (
socket.setKeepAlive()) on the relay WebSocket connection. Intermediate infrastructure (home router NAT, ISP gateways, load balancers) drops the idle TCP connection after their timeout window (typically 60-300 seconds).This matches the classic WebSocket timeout pattern described at websocket.org/guides/troubleshooting/timeout: connections die silently without FIN/RST when intermediate devices drop idle NAT mappings.
Evidence (from
~/.factory/logs/droid-log-single.log)Drops consistently reoccur, with intervals of ~2-14 minutes between reconnects:
The reconnection is fast (~1 second), but the relay's orphan grace window appears to be ~2 seconds, which is too tight for this reconnect frequency.
Environment
setKeepAlive()for these to take effectProposed Fix
In the daemon's relay WebSocket connection code, add one or both of:
TCP keepalive (minimal, catches dead connections after ~30s):
WebSocket-level ping/pong (proper fix, keeps intermediate proxies alive):
Both are one-liners. The
net-keepalivenpm package provides portableTCP_KEEPIDLE/TCP_KEEPINTVLfor finer control if needed.Impact
Every web-initiated session on this BYOM computer breaks after the first message exchange. Local CLI sessions work fine because they go through the daemon's local transport, not the relay WebSocket.