Skip to content

fix(desktop): WSL backend connects with Docker bridges and slow cold starts - #6129

Open
rehanhaider wants to merge 4 commits into
pingdotgg:mainfrom
rehanhaider:fix/wsl-connection
Open

fix(desktop): WSL backend connects with Docker bridges and slow cold starts#6129
rehanhaider wants to merge 4 commits into
pingdotgg:mainfrom
rehanhaider:fix/wsl-connection

Conversation

@rehanhaider

@rehanhaider rehanhaider commented Aug 11, 2026

Copy link
Copy Markdown

What Changed

  • getDistroIp now prefers the default route’s source address, with hostname -I as the fallback.
  • DEFAULT_BACKEND_READINESS_TIMEOUT increases from 1 to 3 minutes.

Scope: two files, 19 lines, two commits. No tests added—the changes are one shell string and one constant.

Why two commits and not one?

Both commits fix the same user-visible hang, so they ship together.

Both causes the exact same symptoms to the user.

Happy to split if you'd rather take them separately.

Why

On Windows, the desktop can remain on “Connecting to WSL…” when Docker is running inside the distro. The backend starts, listens, and answers readiness checks, but the app targets the wrong address and times out too early. Either bug can cause the hang.

Wrong address

getDistroIp previously selected the first IPv4 address from hostname -I, which lists all bound addresses in interface order. With Docker running, a br-* or docker0 bridge can appear first, directing the renderer to an address Windows cannot route to.

This also breaks mirrored networking. isLocalHostIpv4 in DesktopBackendConfiguration maps the backend to loopback only when the reported address belongs to a Windows interface. A Docker bridge does not match, so mirrored-mode users lose the intended loopback path.

The new command asks the kernel for the default route’s source address using ip -4 route get 192.0.2.1. This avoids interface-name matching and RFC1918 heuristics without adding another spawn. The route source is printed before hostname -I, allowing the existing first-match parser and IPv4 validation to remain unchanged.

hostname -I remains the fallback, so distros without iproute2 or a default route behave as they do today. 192.0.2.1 is reserved by RFC 5737, and this is only a routing-table lookup—no packet is sent.

Readiness timed out too early

Readiness currently receives a single 60-second budget. Loading the server bundle and dependencies across /mnt/c regularly takes longer. When the probe expires, the backend process remains alive but is never marked ready, leaving the splash visible even if startup completes seconds later.

The probe polls every 100 ms and resolves immediately when the backend responds, so increasing the budget does not slow fast launches.

Verification

Verified on Windows 11 with WSL2 mirrored networking and three active Docker bridges—the topology reported in #5211:

hostname -I   ->  172.18.0.1  172.17.0.1  172.19.0.1  192.168.1.5
route source  ->  192.168.1.5

Before the timeout change, with the address fix already applied:

  • Two launches abandoned readiness at exactly 60,000 ms.
  • The backend was listening and answering HTTP 200 at 98 seconds.

After both changes:

  • isLocalHostIpv4 matches 192.168.1.5 to the host Wi-Fi adapter.
  • Readiness targets http://127.0.0.1:.
  • The backend returns HTTP 200 and the desktop connects.

The address fix is evidenced by the route lookup above: hostname -I returns the bridge first, so the previous first-match selection resolves to 172.18.0.1, which Windows cannot route to. Measurements were taken against a dev build (dev:desktop), which also waits on the Vite server, so the 98s figure is not directly a packaged-build number.

Validation:

vp test run apps/desktop/src/wsl/DesktopWslEnvironment.test.ts apps/desktop/src/backend/DesktopBackendManager.test.ts

All 52 tests passed. Typecheck and lint are clean for both changed files.

Related Work

This area is active:

There could be more robustness in the solution but I have preferred simple, tested, and workable solution instead.

Closes #5211
Refs #4535 and #5522.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • No UI, animation, or interaction changes

Note

[!NOTE]

Fix WSL distro IP detection and extend backend readiness timeout

  • Updates getDistroIpImpl in DesktopWslEnvironment.ts to prefer the IPv4 source address of the default route (ip -4 route get 192.0.2.1) before falling back to hostname -I, which is more reliable in multi-interface WSL environments.
  • Increases DEFAULT_BACKEND_READINESS_TIMEOUT in DesktopBackendManager.ts from 1 minute to 3 minutes to accommodate slower WSL startup times.
  • Behavioral Change: the IP returned by getDistroIpImpl may differ in environments with multiple network interfaces.

Macroscope summarized cce7a8b.

getDistroIp took the first IPv4 from `hostname -I`, which prints every
bound address in interface order. A distro running Docker reports its
br-*/docker0 bridges first, so the desktop pointed the renderer at an
address Windows cannot reach and sat on "Connecting to WSL..." until the
readiness budget expired. It also defeated the mirrored-networking check
in DesktopBackendConfiguration, which only collapses to loopback when
the reported address belongs to a Windows interface; a bridge address
never matches.

Print the default route's source address ahead of the `hostname -I`
output so the existing first-match parser picks it up. `hostname -I`
still trails it, so distros without iproute2, or with no default route,
keep today's behaviour. The lookup targets 192.0.2.1 (RFC 5737) and is a
routing-table query, so no packet is sent.

Verified on Windows 11, WSL2 mirrored networking, three Docker bridges:

  hostname -I  ->  172.18.0.1 172.17.0.1 172.19.0.1 192.168.1.5
  route source ->  192.168.1.5

Before, readiness targeted the 172.18.0.1 bridge and never resolved.
After, isLocalHostIpv4 matches 192.168.1.5 against the host adapter and
readiness targets http://127.0.0.1:<port>, which answers 200.

Closes pingdotgg#5211.
The desktop probes backend readiness once with a 60s budget. A WSL
backend loads the server bundle and its dependencies across /mnt/c, so
cold boot regularly exceeds that. When it does the probe gives up, the
run is left alive but never ready, and the app sits on "Connecting to
WSL..." forever even though the backend finishes booting seconds later
and answers 200 on its readiness endpoint.

Measured on Windows 11 with WSL2, two consecutive launches:

  run 1: gave up at 60000ms; backend healthy shortly after
  run 2: gave up at 60000ms; backend listening and answering 200 at 98s

The probe polls every 100ms and resolves the instant the backend
answers, so a larger budget costs nothing when boot is fast.

Refs pingdotgg#4535, pingdotgg#5522.
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d9d2e26b-2946-43d9-a691-b7e7054c3b99

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:S 10-29 changed lines (additions + deletions). labels Aug 11, 2026
@macroscopeapp

macroscopeapp Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies WSL connection logic, including how the distro IP is determined. While the intent is to fix connection issues, changes to IP detection on the critical connection path warrant human verification to ensure correctness across different WSL configurations.

You can customize Macroscope's approvability policy. Learn more.

@rehanhaider rehanhaider changed the title Fix/wsl connection fix(desktop): WSL backend connection hangs due to preflight timeout and incorrect IP lookup Aug 11, 2026
@rehanhaider rehanhaider changed the title fix(desktop): WSL backend connection hangs due to preflight timeout and incorrect IP lookup fix(desktop): WSL backend connects with Docker bridges and slow cold starts Aug 11, 2026
@rehanhaider

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cce7a8bf82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"--",
"sh",
"-c",
"ip -4 route get 192.0.2.1 2>/dev/null | sed -n 's/.* src \\([0-9.]*\\).*/\\1/p'; hostname -I",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add focused tests for the new WSL address selection

This changes backend IP-selection behavior but adds no test exercising the new ordered output. In the reported Docker scenario, a spawner-backed test should verify that the route source wins when hostname -I lists a bridge first, and that the hostname result still wins when iproute2 is unavailable; otherwise shell quoting or output-order regressions can restore the inaccessible address while the existing suite remains green.

AGENTS.md reference: AGENTS.md:L108-L108

Useful? React with 👍 / 👎.

"--",
"sh",
"-c",
"ip -4 route get 192.0.2.1 2>/dev/null | sed -n 's/.* src \\([0-9.]*\\).*/\\1/p'; hostname -I",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid selecting a VPN route source as the WSL address

When a NAT-mode distro routes 192.0.2.1 through a VPN or exit-node policy table, ip route get returns the tunnel interface's source rather than the Windows-facing WSL address. The first-match parser then passes that address to resolveWslStartConfig, which uses it directly for the readiness and renderer URL; if Windows has no route to the distro's VPN address, the backend again remains stuck connecting. Select the Windows-facing interface/default gateway path rather than the source chosen for an arbitrary external destination.

Useful? React with 👍 / 👎.

const MAX_PREFLIGHT_FAILURE_ATTEMPTS = 5;
const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(1);
// One-shot probe; a WSL backend over /mnt/c regularly needs 60-98s to answer.
const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(3);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add a focused test for the extended readiness budget

The intended slow-cold-start behavior is not covered by the existing readiness test, which supplies its own 50 ms timeout and therefore cannot detect this default being reverted or wired to the wrong backend path. Add a TestClock manager test where readiness succeeds after more than one minute but before three minutes and assert that onReady fires; otherwise this backend behavior change can regress while the focused desktop suite remains green.

AGENTS.md reference: AGENTS.md:L108-L108

Useful? React with 👍 / 👎.

@CDVolvik CDVolvik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the IP half on a real WSL2 distro. The new command emits two lines, the route source then the hostname -I list, and the existing parser is

const candidate = raw.split(/\s+/).find((part) => IPV4_PATTERN.test(part));

which splits on all whitespace including newlines and takes the first match, so the route source wins and the hostname -I tail stays a fallback exactly as intended. Worth stating explicitly in the comment, because the fix silently depends on that parser taking the first match across the whole output rather than parsing a single line. If anyone later "tidies" it to read one line or to prefer the last match, this regresses without a test noticing.

The fallback chain also holds up: no iproute2 means sed gets nothing and hostname -I carries it, and no default route means no src field and the same fallback. Both degrade to today's behaviour rather than to nothing.

The timeout half is the part I would push back on:

-const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(1);
+// One-shot probe; a WSL backend over /mnt/c regularly needs 60-98s to answer.
+const DEFAULT_BACKEND_READINESS_TIMEOUT = Duration.minutes(3);

The justification is specific to a WSL backend running off /mnt/c, but the constant is the default for every backend. After this, a local backend that is simply dead keeps the app in "connecting" for three minutes instead of one, on machines that never had the problem. That is a real cost paid by the majority to fix a case the code can already distinguish, since the WSL path is a separate branch and could carry its own readiness timeout.

Worth flagging too that #5042 and #5769 both go after the cause rather than the symptom, by getting the backend off /mnt/c and onto the Linux filesystem. If either lands, the 60-98s figure this constant is sized against disappears, and a tripled global timeout is left behind as something nobody remembers to put back. A WSL-scoped override would not have that problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop stuck on "Connecting to WSL" — getDistroIp picks unreachable Docker bridge IP instead of eth0

2 participants