Skip to content

fix: send wake request to tunnel on system wake#262

Draft
EhabY wants to merge 5 commits into
mainfrom
fix/send-wake-request-on-system-wake
Draft

fix: send wake request to tunnel on system wake#262
EhabY wants to merge 5 commits into
mainfrom
fix/send-wake-request-on-system-wake

Conversation

@EhabY

@EhabY EhabY commented Jul 13, 2026

Copy link
Copy Markdown

After macOS sleep/wake, Coder Connect can stay unusable for minutes when no link-change event fires, leaving path re-discovery to the periodic re-STUN timer (coder/coder#26736).

The daemon-side hook merged in coder/coder#26739 (WakeRequest/WakeResponse, tunnel protocol 1.3). This PR makes Coder Desktop emit it:

  • The helper daemon registers for IOKit power events via IORegisterForSystemPower and reacts only to kIOMessageSystemHasPoweredOn, which is delivered on full wake only, never on dark wake.
  • Speaker now advertises protocol 1.3 and records the negotiated version.
  • On wake, the manager sends WakeRequest over the existing tunnel RPC channel, gated on negotiated version >= 1.3 so older daemons are never sent a message they don't understand. Failures are logged; nothing is torn down.
  • vpn.proto synced with the wake messages and vpn.pb.swift regenerated with protoc-gen-swift 1.28.2 (matching the pinned SwiftProtobuf version).

Why IOKit instead of NSWorkspace.didWakeNotification?

The helper is a root LaunchDaemon with no GUI login session, and NSWorkspace notifications are only delivered to processes inside one (apps/LaunchAgents). IOKit power notifications are delivered to daemons, and kIOMessageSystemHasPoweredOn has the same full-wake-only semantics as didWakeNotification (https://developer.apple.com/forums/thread/770517). Since the helper owns the tunnel lifecycle, the wake kick works even when the menu-bar app isn't running.

Tests

  • ProtoVersion comparison (Comparable).
  • Handshake exposes the negotiated version.
  • Speaker.wake() round-trip over an in-memory pipe.
  • Speaker.wake() is a no-op against a 1.1 peer.
Decision log
  • Version gating: Speaker.handshake() previously discarded the negotiated version; it now stores it. The wake RPC lives in a Speaker extension typed to the manager role so the gate is unit-testable; logging happens in Manager.wake() like the other RPCs.
  • WakeResponse.success: kept in the proto for wire parity with upstream, but not read by the client.
  • Proto sync: applied only the wake additions instead of a full upstream file sync; upstream reformatted to tabs and added Windows-only StartProgress messages, both of which would add noise. Generated bindings are additive only.
  • Debounce: none client-side; the daemon already debounces wake-triggered rebinds (5s) per fix: add VPN wake rebind hook coder#26739.

Closes #260


Generated by Coder Agents on behalf of @EhabY.

@EhabY EhabY marked this pull request as draft July 13, 2026 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Send CoderVPN WakeRequest on system wake to recover tunnel paths

1 participant