gravitational/teleport · warning
VNet is already running
Error message
VNet is already running
What it means
errAlreadyRunning is a sentinel error in the VNet daemon package (lib/vnet/daemon/common_darwin.go:42). It is produced when the macOS VNet daemon receives a start request while an instance of the daemon is already running. The daemon maps the Objective-C error in the custom vnetErrorDomain with code VNEAlreadyRunningError to this Go sentinel, and the client translates it back when it sees that domain/code from C.StartVnet.
Source
Thrown at lib/vnet/daemon/common_darwin.go:42
import "C"
import (
"errors"
"unsafe"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/lib/utils/darwinbundle"
)
var (
// vnetErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet.
vnetErrorDomain = C.GoString(C.VNEErrorDomain)
// errorCodeAlreadyRunning is returned within [vnetErrorDomain] errors to indicate that the daemon
// received a message to start after it was already running.
errorCodeAlreadyRunning = int(C.VNEAlreadyRunningError)
errAlreadyRunning = errors.New("VNet is already running")
// errorCodeMissingCodeSigningIdentifiers is returned within [vnetErrorDomain] Obj-C errors and
// transformed to [errMissingCodeSigningIdentifiers] in Go.
errorCodeMissingCodeSigningIdentifiers = int(C.VNEMissingCodeSigningIdentifiersError)
errMissingCodeSigningIdentifiers = errors.New("either identifier or team identifier is missing in code signing information; is the binary signed?")
)
var (
// nsCocoaErrorDomain is a generic error domain used in a lot of Apple's Cocoa frameworks.
nsCocoaErrorDomain = "NSCocoaErrorDomain"
// https://developer.apple.com/documentation/foundation/nsxpcconnectioninterrupted-swift.var
errorCodeNSXPCConnectionInterrupted = int(C.NSXPCConnectionInterrupted)
errXPCConnectionInterrupted = errors.New("XPC connection interrupted")
// https://developer.apple.com/documentation/foundation/nsxpcconnectioncodesigningrequirementfailure-swift.var
errorCodeNSXPCConnectionCodeSigningRequirementFailure = int(C.NSXPCConnectionCodeSigningRequirementFailure)
errXPCConnectionCodeSigningRequirementFailure = errors.New("code signing requirement failed")View on GitHub (pinned to 1283425b60)
Solutions
- Wait and retry: the client already waits 2*CheckUnprivilegedProcessInterval and calls the daemon again, so a transient race usually self-heals.
- Check if a daemon instance is already running (launchctl print / ps for the VNet daemon job) and stop it before starting a new one.
- If a stale daemon persists, reboot or explicitly bootout/kick the launchd job for the tsh daemon label.
Example fix
// before: starting VNet unconditionally
if err := client.RegisterAndCall(ctx, bundlePath, cfg); err != nil { return err }
// after: tolerate the already-running case in the caller
if err := client.RegisterAndCall(ctx, bundlePath, cfg); err != nil {
if errors.Is(err, vnetdaemon.ErrAlreadyRunning) {
return nil // VNet already active, nothing to do
}
return trace.Wrap(err)
} Defensive patterns
Strategy: retry
Validate before calling
// check for an existing daemon job before starting
out, err := exec.Command("launchctl", "print", "system/"+daemonLabel).CombinedOutput()
if err == nil && !bytes.Contains(out, []byte("could not find service")) {
// daemon already loaded; skip start or stop it first
} Type guard
func isAlreadyRunning(err error) bool { return errors.Is(err, vnetdaemon.ErrAlreadyRunning) } Try / catch
if err := client.RegisterAndCall(ctx, bundlePath, cfg); err != nil {
if errors.Is(err, vnetdaemon.ErrAlreadyRunning) {
// benign: daemon already active; optionally wait and retry once
return nil
}
return trace.Wrap(err)
} Prevention
- Check daemon status (SMAppService/launchctl) before attempting to start.
- Avoid rapid stop/start cycles; give the old daemon time to exit.
- Serialize VNet start operations within the same user session.
When it happens
Trigger: Calling tsh VNet start (RegisterAndCall -> startByCalling) while a previous VNet daemon instance is still alive; or restarting VNet so quickly that the old daemon has not yet noticed the previous instance stopped and exited (startByCalling returns the error and RegisterAndCall waits 2*CheckUnprivilegedProcessInterval then retries once).
Common situations: Running `tsh vnet start` in a second terminal while VNet is already active; running VNet on two tsh clients under the same macOS user; stop-then-immediately-start races; stale daemon left running after a crash of the client process.
Related errors
- could not connect to the VNet daemon within the timeout
- XPC connection interrupted
- code signing requirement failed
- XPC connection invalid
- either identifier or team identifier is missing in code sign
AI-assisted analysis of gravitational/teleport@1283425b60 (2026-09-02).
Data as JSON: /api/errors/4c2e26dec171b924.
Report an issue: GitHub.