SIGN IN SIGN UP

tsnet: don't panic in Close when Start failed before s.sys was set

Server.close() dereferenced s.sys.Bus unconditionally. s.sys is only
assigned partway through doInit, so it is still nil when Start fails
before that point, and Close then panics with a nil pointer dereference.

Close is reachable in that state: Start is idempotent via initOnce and
returns the stored initErr, so callers that `defer Close()` before
checking the error land in close() with s.sys == nil. Every other
resource released nearby (s.dialer, s.localAPIListener,
s.loopbackListener) is already nil-guarded; this line was the outlier.

This is reachable in practice from libtailscale/TailscaleKit, where a
failed tsnet start on darwin is followed by a close and surfaces as
EXC_BAD_ACCESS rather than the underlying start error.

Use GetOK instead of Get as well, so an unset Bus returns ok=false
rather than panicking via SubSystem.Get.

Adds TestCloseBeforeStart, which panics without this change.

Updates #20988

Signed-off-by: Prakash Rudraraju <1471544+prakashrj@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
P
Prakash Rudraraju committed
ec2ca5964b1693652d1c5bb83b6f3d6173fbd44d
Parent: d7253cb
Committed by Brad Fitzpatrick <brad@danga.com> on 8/26/2026, 8:47:29 PM