diff --git a/client/internal/connect.go b/client/internal/connect.go index 020d87b7d..7e444ad49 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -483,6 +483,18 @@ func (c *ConnectClient) IsRunning() bool { return c.sup.isRunning() } +// ServiceRunning reports whether the client's lifecycle supervisor is alive and +// able to accept start/stop commands — i.e. the daemon-lifetime client exists +// and its context has not been cancelled. It is independent of whether a run is +// currently up (that is IsRunning). Nil-safe, so callers can ask it on a +// not-yet-constructed client and treat false as "service not running". +func (c *ConnectClient) ServiceRunning() bool { + if c == nil || c.sup == nil { + return false + } + return c.sup.ctx.Err() == nil +} + func (c *ConnectClient) Engine() *Engine { if c == nil { return nil diff --git a/client/server/server.go b/client/server/server.go index fdbf9e625..7ea4cae3f 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -656,9 +656,14 @@ func (s *Server) WaitSSOLogin(callerCtx context.Context, msg *proto.WaitSSOLogin func (s *Server) Up(callerCtx context.Context, msg *proto.UpRequest) (*proto.UpResponse, error) { s.mutex.Lock() - // The client is built once in New(); a nil here means the service was never - // started. Fail loud rather than lazily creating it. - if s.connectClient == nil { + // The client (and its supervisor) is built once in New(), so a nil here + // never happens in production — Up is only reachable after New() has run and + // the gRPC server is serving. The real case this guards is the daemon + // SHUTTING DOWN: rootCtx is cancelled, the supervisor is no longer accepting + // commands, so ServiceRunning() is false even though the client exists. Bail + // loud instead of enqueuing a run that will never start. (nil only happens in + // tests that build a Server without New(); ServiceRunning is nil-safe.) + if !s.connectClient.ServiceRunning() { s.mutex.Unlock() return nil, fmt.Errorf("service is not running, start the netbird service for 'up' to take effect") }