fix(gateway): start on loopback when the interface to listen on is not there yet - #206
Merged
Merged
Conversation
…t there yet twcore used to resolve `listen.gateway.bind` once before starting and exit when the interface was missing or offline. On Windows the desktop app can bind the gateway to the WSL virtual adapter (`vEthernet (WSL)`) so that clients inside WSL reach it under NAT networking. That adapter only exists once WSL has started and gets a new address every time WSL restarts, so a gateway started at login could not start at all, and every client on the machine lost it along with the WSL ones. Resolution now happens inside the listener: when the interface is not there, or a fixed address is not on this machine yet, the gateway listens on loopback only and says why in the listen status. It asks the system again every three seconds and adds the interface once it appears, moves to its new address when it changes, and drops back to loopback when it goes away. A loopback that cannot be bound (the port is taken) is still fatal at startup, as before. A periodic check never asks a retiring listener to give its port up first; that dance briefly stops accepting connections, and repeating it every few seconds against a port someone else holds would turn into a periodic outage. Only a real relisten (config change, save) does it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JpGLt1RZLUuYEXYwao3yGk
This was referenced Sep 25, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by F · project thread
What this changes
Before: when
listen.gateway.bindnames an interface that is missing or offline (or a fixed address that is not on this machine),twcore serveexits at startup.After: the gateway starts on loopback only and reports why in the listen status (
gw.listen.no_such_nic/gw.listen.nic_offline/gw.listen.addr_unavailable). It re-resolves every 3 seconds, adds the interface once it appears, moves to its new address when it changes, and drops back to loopback when it goes away. Loopback is never touched. A loopback that cannot be bound (port taken) is still fatal at startup, as before.Why
On Windows, ThinkWatch Lite's WSL adoption (Lite #216) binds the gateway to the WSL virtual adapter (
vEthernet (WSL)) under NAT networking. That adapter only exists once WSL has started and gets a new address every WSL restart. A gateway autostarted at login therefore could not start at all, taking every client on the machine down with it.How it was verified
crates/tw-gateway/tests/hotreload.rs: missing interface starts on loopback and later switches to a resolvable config; a fixed address not on the machine starts on loopback; a taken loopback port still failsserve_at; a port change while the interface is missing moves loopback to the new port. hotreload 23/23,tw-control --test listen8/8.twcore servewithbind: twtest0before the interface existed → listened on127.0.0.1only with a warning; created it with 10.98.0.1 → listener added within 3 s; re-addressed to 10.98.0.7 → moved; deleted → back to loopback only; recreated with 10.98.0.9 → added again. Loopback answered throughout.cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo test --workspace(88 suites),scripts/smoke.sh60 passed / 0 failed. Locally the clippy run needed-A clippy::nonminimal_boolfor a pre-existing lint intw-dialect/src/convert.rsthat my older local toolchain (1.94) reports; untouched here.Notes for review
serve_atnow takes theGatewayListeninstead of pre-resolved addresses, so resolution lives in one place.SaveListenstill rejects an interface that is missing at save time (409); unchanged.🤖 Generated with Claude Code
https://claude.ai/code/session_01JpGLt1RZLUuYEXYwao3yGk
Generated by Claude Code