{"record":{"id":"fc266e3fe39b6138","repo":"tailscale/tailscale","slug":"v-v","errorCode":null,"errorMessage":"%v: %v","messagePattern":"%v: %v","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"derp/derphttp/derphttp_client.go","lineNumber":387,"sourceCode":"\t\t\tcancel()\n\t\t}\n\t}()\n\tdefer cancel()\n\n\tvar reg *tailcfg.DERPRegion // nil when using c.url to dial\n\tif c.getRegion != nil {\n\t\treg = c.getRegion()\n\t\tif reg == nil {\n\t\t\treturn nil, 0, errors.New(\"DERP region not available\")\n\t\t}\n\t}\n\n\tvar tcpConn net.Conn\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif ctx.Err() != nil {\n\t\t\t\terr = fmt.Errorf(\"%v: %v\", ctx.Err(), err)\n\t\t\t}\n\t\t\terr = fmt.Errorf(\"%s connect to %v: %v\", caller, c.targetString(reg), err)\n\t\t\tif tcpConn != nil {\n\t\t\t\tgo tcpConn.Close()\n\t\t\t}\n\t\t}\n\t}()\n\n\tvar node *tailcfg.DERPNode // nil when using c.url to dial\n\tvar idealNodeInRegion bool\n\tswitch {\n\tcase canWebsockets && useWebsockets():\n\t\tvar urlStr string\n\t\tif c.url != nil {\n\t\t\turlStr = c.url.String()\n\t\t} else {\n\t\t\turlStr = c.urlString(reg.Nodes[0])\n\t\t}","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/derp/derphttp/derphttp_client.go#L369-L405","documentation":"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: ...\".","triggerScenarios":"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).","commonSituations":"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.","solutions":["Read the message: 'context deadline exceeded' first means the timeout was too small — measure handshake time and enlarge the deadline.","'context canceled' means your own code closed/canceled — make sure you don't Close the client while Connect is pending.","If the underlying dial error is a network fault, fix reachability first; the ctx wrap only tells you the attempt was aborted."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"// isCtxCanceled reports whether the dial failed because ctx was done\n// (deadline exceeded or canceled) rather than a network fault.\nfunc isCtxCanceled(ctx context.Context, err error) bool {\n\treturn ctx.Err() != nil && err != nil && strings.Contains(err.Error(), ctx.Err().Error())\n}","tryCatchPattern":"conn, err := dialDERP(ctx)\nif err != nil {\n    if isCtxCanceled(ctx, err) {\n        // Aborted by our own deadline/cancel: retry with a longer budget\n        // unless the parent is shutting down.\n        if ctx.Err() == context.Canceled {\n            return err // shutdown; do not retry\n        }\n        ctx, cancel = context.WithTimeout(parent, 3*timeout)\n        defer cancel()\n        conn, err = dialDERP(ctx)\n    }\n}","preventionTips":["Size connect deadlines for worst-case TCP+TLS handshake (add headroom on lossy links), not the happy path.","Never Close/Cancel the parent client while a Connect is in flight; sequence shutdown after dial completion.","Distinguish context.Canceled (shut down, stop) from context.DeadlineExceeded (retry with more budget) in retry logic."],"tags":["derphttp","context","timeout","go","dial"],"backgroundTag":"context-deadline-exceeded","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}