tailscale/tailscale · error

%v: %v

Error message

%v: %v

What it means

The inner wrapper in derphttp's dial path: when a connect attempt fails AND the passed context is already done, the error is rewrapped as "<ctx.Err()>: <original err>". So this message means the context was canceled or its deadline was exceeded while dialing; the text after the colon is what the dial itself reported. The outer layer then adds "%s connect to %v: ...".

Source

Thrown at derp/derphttp/derphttp_client.go:387

			cancel()
		}
	}()
	defer cancel()

	var reg *tailcfg.DERPRegion // nil when using c.url to dial
	if c.getRegion != nil {
		reg = c.getRegion()
		if reg == nil {
			return nil, 0, errors.New("DERP region not available")
		}
	}

	var tcpConn net.Conn

	defer func() {
		if err != nil {
			if ctx.Err() != nil {
				err = fmt.Errorf("%v: %v", ctx.Err(), err)
			}
			err = fmt.Errorf("%s connect to %v: %v", caller, c.targetString(reg), err)
			if tcpConn != nil {
				go tcpConn.Close()
			}
		}
	}()

	var node *tailcfg.DERPNode // nil when using c.url to dial
	var idealNodeInRegion bool
	switch {
	case canWebsockets && useWebsockets():
		var urlStr string
		if c.url != nil {
			urlStr = c.url.String()
		} else {
			urlStr = c.urlString(reg.Nodes[0])
		}

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Read the message: 'context deadline exceeded' first means the timeout was too small — measure handshake time and enlarge the deadline.
  2. 'context canceled' means your own code closed/canceled — make sure you don't Close the client while Connect is pending.
  3. If the underlying dial error is a network fault, fix reachability first; the ctx wrap only tells you the attempt was aborted.
Defensive patterns

Strategy: retry

Type guard

// isCtxCanceled reports whether the dial failed because ctx was done
// (deadline exceeded or canceled) rather than a network fault.
func isCtxCanceled(ctx context.Context, err error) bool {
	return ctx.Err() != nil && err != nil && strings.Contains(err.Error(), ctx.Err().Error())
}

Try / catch

conn, err := dialDERP(ctx)
if err != nil {
    if isCtxCanceled(ctx, err) {
        // Aborted by our own deadline/cancel: retry with a longer budget
        // unless the parent is shutting down.
        if ctx.Err() == context.Canceled {
            return err // shutdown; do not retry
        }
        ctx, cancel = context.WithTimeout(parent, 3*timeout)
        defer cancel()
        conn, err = dialDERP(ctx)
    }
}

Prevention

When it happens

Trigger: dialNode/dialRegion exceeding the caller's ctx deadline (tight timeout, slow network), or the parent context being canceled mid-dial (client Close, shutdown, region switch).

Common situations: Context deadline set shorter than TCP+TLS handshake time on lossy links; the DERP client closed while a background connect was in flight; prober cancellations.

Related errors


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/fc266e3fe39b6138. Report an issue: GitHub.