#19026: fix(gateway): use loopback for local CLI-to-gateway connections
gateway
commands
size: S
Cluster:
Gateway Resilience and Configuration
## Summary
Fixes #19004.
- When `gateway.bind=lan`, the CLI constructed its WebSocket URL using the host's LAN IP (e.g. `ws://192.168.1.42:18789`). The gateway's `isLocalDirectRequest()` only recognizes loopback/localhost Host headers as "local", so the CLI was treated as a remote connection requiring device pairing — a chicken-and-egg problem.
- `localUrl` now always uses `127.0.0.1`. The `urlSource` diagnostic string still reports the LAN/tailnet IP for display purposes.
- The `bind` mode controls which interfaces the **server** listens on — `0.0.0.0` always accepts loopback, so co-located clients should always connect via loopback.
## Test plan
- [x] `npx vitest run src/gateway/call.test.ts` — all 21 tests pass
- [x] `npx oxfmt --check` and `npx oxlint` — clean
- [x] Manual CLI test: set `gateway.bind=lan`, ran `gateway probe` and `gateway health` — connects via `ws://127.0.0.1:18789` and all channels report OK
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes a chicken-and-egg problem where the CLI, when configured with `gateway.bind=lan` or `bind=tailnet`, would construct WebSocket URLs using the LAN/tailnet IP (e.g., `ws://192.168.1.42:18789`). The gateway's `isLocalDirectRequest()` only recognizes loopback/localhost Host headers as local, so the CLI was incorrectly treated as a remote connection requiring device pairing.
- `localUrl` in `buildGatewayConnectionDetails` now always uses `127.0.0.1`. Since LAN mode binds to `0.0.0.0` (which accepts loopback), co-located CLI clients connect correctly.
- The `urlSource` diagnostic string still reports the LAN/tailnet IP for display purposes, preserving useful debugging output.
- Tests updated to match new behavior, though three test names are now misleading (they describe old tailnet/LAN behavior while asserting loopback URLs).
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — it's a focused, well-reasoned fix for a clear bug with no risk of regression.
- The change is minimal and directly addresses the documented issue. The logic is sound: LAN/tailnet bind modes use `0.0.0.0` which accepts loopback, so local CLI clients should always connect via loopback to pass the `isLocalDirectRequest()` check. Only style concern is the now-misleading test names.
- No files require special attention. `src/gateway/call.test.ts` has minor test naming issues but no logic problems.
<sub>Last reviewed commit: f335df4</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#22056: fix(gateway): use loopback for self-connections regardless of bind ...
by usedhonda · 2026-02-20
89.6%
#19437: Gateway: respect custom bind host for local health/RPC target resol...
by frudas24 · 2026-02-17
85.7%
#22110: fix(tools): prefer loopback for internal tool-to-gateway RPC calls
by pierreeurope · 2026-02-20
85.1%
#22804: fix: prioritize loopback for internal gateway calls (issue #22706)
by ambicuity · 2026-02-21
83.7%
#22343: fix(gateway): treat private LAN hosts as local direct
by AIflow-Labs · 2026-02-21
82.5%
#14564: fix(gateway): crashes on startup when tailscale meets non-loopback ...
by yinghaosang · 2026-02-12
81.7%
#16300: fix(tui): respect gateway bind mode in TUI connection
by cortexuvula · 2026-02-14
81.7%
#21436: fix(gateway): plaintext ws:// blocked for Docker bind=lan (SECURITY...
by xinhuagu · 2026-02-19
80.7%
#21842: fix(gateway-cli): use wss:// scheme when gatewayTls is enabled
by hydro13 · 2026-02-20
80.4%
#21233: docs: clarify bind=lan non-loopback access requires wss or tunnel (...
by saurabhchopade · 2026-02-19
79.1%