Cloud legacy link: bound and log every start step, show the failing step with Retry
Cloud start on the legacy link (what production builds use) could wait 3 minutes and then fail with "didn't connect" and no reason. In the report: "Nightly has no wg hub or cmux-tui remote connect process for any Cloud machine, and it logs no link error ... It took 182.8 s, which matches the 6×30 s connect wait."
Root cause. Every legacy link joins one shared tunnel-hub start (POST /api/vm/tunnel enroll, then spawn the hub). That start had no overall bound. The enroll's token read sits outside the API client's own deadline, and withDeadline waits for cancelled children. So if one start stalled, every link waiting on it stalled too, with nothing spawned and nothing logged. That matches the report: no hub and no remote connect for any machine, and no error.
Fix
- Each legacy link step has its own deadline and log lines: attach-endpoint 45 s, hub 120 s (enroll 75 s), approve 75 s, remote connect 75 s. The new
CloudLinkStart.swiftusesSandboxDeadline.race, which answers at the bound even when the work ignores cancellation. Logs (cloud.link, notice level) cover start, step began/done, pid spawned, link up, superseded, and failed at after N s: . - A failure records its step and reason on the session (
legacyLinkFailure). A definitive one (signed out, plan required, 4xx, missing binary, hub error) ends the 180 s wait at once. A timeout or transport error triggers one redial (retryNow), and the wait goes on. - The "Moving this chat to Cloud…" toast now says " didn't connect: failed ()." and offers Retry, which reruns the start. Its duration is 30 s with Retry and 12 s without. The toast then ends.
attach()redials a live legacy machine that has no connection.- The new strings are translated in all locales.
The app-server link path and classic Cloud are untouched.
Risk. The main risk is in the legacy Cloud start: the deadlines could cut off a slow but healthy start. The bounds sit well above measured times (attach-endpoint about 0.6 s, enroll about 1.7 s, link up about 1 s on a production account).
Tests. CloudLinkStartTests cover: the deadline firing on work that ignores cancellation; the step and reason surviving; the definitive classification; pass-through; and progress state. Compile check is green for the app and cmux-tui at the head.
Read-back. A production-auth build on a fleet Mac signed in and brought up about 20 legacy links, each logged per step ("link up in 1.1 s"). One machine failed and the log names it: "failed at remote-connect after 20.3 s: remote connection did not become ready". A Cloud start created its machine and copied the folder over the legacy link.
Related: SSH host-key failure filed as #33204.
Agent session: agent session
🤖 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the legacy Cloud link start stalling silently: one stalled shared tunnel-hub start blocked every link for minutes, then failed with "didn't connect" and no reason.
- Each link step (attach-endpoint, hub, approve, remote connect) now runs under its own deadline with step-level log lines, so a start that doesn't come up names the step and reason.
- A definitive failure (signed out, plan required, 4xx, missing binary, hub error) ends the 180 s wait at once; a timeout or transport error triggers one redial.
- The "didn't connect" toast now names the failing step and offers Retry, which reruns the start; a new wait or Retry clears the earlier attempt's failure so a stale one can't answer a fresh wait.
attach()redials a live legacy machine that has no connection.- The app-server link path and classic Cloud are untouched.