Type: AFK — surfaced during review of #53.
Problem
touch_idle fires once per request in handle() (crates/adj/src/proxy.rs), before forward() runs. Once a request upgrades to a WebSocket, bytes flow through the detached copy_bidirectional tunnel and never touch last_request again. Vite/Webpack/Next HMR heartbeat pings ride inside that tunnel, so they don't count as activity either.
Net effect with the 15m idle_timeout default: a browser tab sitting on a dev server over HMR — no other HTTP traffic — looks idle to the scanner. After 15m the scanner SIGTERMs the app; the HMR client reconnects, lazy-boots it again, and the cycle repeats every 15m. Self-healing, but a restart loop is exactly the churn Adjacent exists to prevent.
(#53 correctly scoped this out; filing so it doesn't evaporate.)
Proposed fix
Make idle tracking tunnel-aware. Options, cheapest first:
- Touch on tunnel close (floor): the tunnel task calls
touch_idle when copy_bidirectional returns, so the idle clock restarts from when the session actually ended rather than when it began.
- Periodic touch while the tunnel is open: a timer at a cadence well under
idle_timeout (e.g. every 60s, or idle_timeout / 4) that stops when the tunnel closes. This is what actually prevents the mid-session kill.
Touch-on-close alone does not prevent the 15m mid-session SIGTERM — it only fixes the clock after the fact. A periodic touch is needed to keep a long-lived HMR session alive. Tradeoff: a naive interval keeps the app alive for any open tunnel, even one carrying no bytes; tying the touch to observed copy progress is more faithful but more code. Pick based on how much that matters in practice.
Acceptance criteria
Type: AFK — surfaced during review of #53.
Problem
touch_idlefires once per request inhandle()(crates/adj/src/proxy.rs), beforeforward()runs. Once a request upgrades to a WebSocket, bytes flow through the detachedcopy_bidirectionaltunnel and never touchlast_requestagain. Vite/Webpack/Next HMR heartbeat pings ride inside that tunnel, so they don't count as activity either.Net effect with the 15m
idle_timeoutdefault: a browser tab sitting on a dev server over HMR — no other HTTP traffic — looks idle to the scanner. After 15m the scanner SIGTERMs the app; the HMR client reconnects, lazy-boots it again, and the cycle repeats every 15m. Self-healing, but a restart loop is exactly the churn Adjacent exists to prevent.(#53 correctly scoped this out; filing so it doesn't evaporate.)
Proposed fix
Make idle tracking tunnel-aware. Options, cheapest first:
touch_idlewhencopy_bidirectionalreturns, so the idle clock restarts from when the session actually ended rather than when it began.idle_timeout(e.g. every 60s, oridle_timeout / 4) that stops when the tunnel closes. This is what actually prevents the mid-session kill.Touch-on-close alone does not prevent the 15m mid-session SIGTERM — it only fixes the clock after the fact. A periodic touch is needed to keep a long-lived HMR session alive. Tradeoff: a naive interval keeps the app alive for any open tunnel, even one carrying no bytes; tying the touch to observed copy progress is more faithful but more code. Pick based on how much that matters in practice.
Acceptance criteria
idle_timeout.idle_timeout.idle_timeout, hold a tunnel open across that window, assert the app staysRunning; then close the tunnel and assert it idle-stops.