{"record":{"id":"67aa85dd059c118c","repo":"gravitational/teleport","slug":"conn-was-closed","errorCode":null,"errorMessage":"conn was closed","messagePattern":"conn was closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/client/client.go","lineNumber":611,"sourceCode":"\treturn func(ctx context.Context, addr string) (net.Conn, error) {\n\t\tif c.isClosed() {\n\t\t\treturn nil, trace.ConnectionProblem(nil, \"client is closed\")\n\t\t}\n\t\tconn, err := c.dialer.DialContext(ctx, \"tcp\", addr)\n\t\tif err != nil {\n\t\t\treturn nil, trace.ConnectionProblem(err, \"failed to dial: %v\", err)\n\t\t}\n\t\treturn conn, nil\n\t}\n}\n\n// waitForConnectionReady waits for the client's grpc connection finish dialing, returning an error\n// if the ctx is canceled or the client's gRPC connection enters an unexpected state. This can be used\n// alongside the DialInBackground client config option to wait until background dialing has completed.\nfunc (c *Client) waitForConnectionReady(ctx context.Context) error {\n\tfor {\n\t\tif c.conn == nil {\n\t\t\treturn errors.New(\"conn was closed\")\n\t\t}\n\t\tswitch state := c.conn.GetState(); state {\n\t\tcase connectivity.Ready:\n\t\t\treturn nil\n\t\tcase connectivity.TransientFailure, connectivity.Connecting, connectivity.Idle:\n\t\t\t// Wait for expected state transitions. For details about grpc.ClientConn state changes\n\t\t\t// see https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md\n\t\t\tif !c.conn.WaitForStateChange(ctx, state) {\n\t\t\t\t// ctx canceled\n\t\t\t\treturn trace.Wrap(ctx.Err())\n\t\t\t}\n\t\tcase connectivity.Shutdown:\n\t\t\treturn trace.Errorf(\"client gRPC connection entered an unexpected state: %v\", state)\n\t\t}\n\t}\n}\n\n// Config contains configuration of the client","sourceCodeStart":593,"sourceCodeEnd":629,"githubUrl":"https://github.com/gravitational/teleport/blob/1283425b60ec5f60d509ba4c791183d452923ff7/api/client/client.go#L593-L629","documentation":"waitForConnectionReady polls the client's gRPC connection state until it becomes Ready. The error \"conn was closed\" is returned when c.conn is nil, meaning the ClientConn field was never set or has been closed/nulled out (e.g. after Client.Close) while someone is still waiting for the connection to become ready. It indicates use of a client whose underlying gRPC channel is gone, not a transient connectivity problem.","triggerScenarios":"Calling any Client RPC (or using DialInBackground then waitForConnectionReady) after the client's gRPC connection has been closed via Client.Close(), or on a Client constructed without a valid connection so c.conn is nil.","commonSituations":"Long-lived processes reusing a Teleport client after shutdown/teardown; race between Close() and in-flight calls; DialInBackground usage where the dial never completes because the conn was torn down; tests or proxies closing clients while background work still runs.","solutions":["Check whether Client.Close() (or process shutdown) was called before this request; recreate the client with teleport.NewClient instead of reusing the closed one.","Ensure the Client is constructed normally (config not leaving conn nil); with DialInBackground, call waitForConnectionReady only on a live client instance.","Fix lifecycle ownership: don't share one client across goroutines where one may close it while others dial/wait; guard with a reference count or context cancellation."],"exampleFix":"// before\ncl, _ := client.New(ctx, cfg)\n_ = cl.Close()\nusers, err := cl.GetUsers(ctx, false) // \"conn was closed\"\n\n// after\ncl, _ := client.New(ctx, cfg)\ndefer cl.Close()\nusers, err := cl.GetUsers(ctx, false)","handlingStrategy":"try-catch","validationCode":"if cl == nil || cl.IsClosed() { cl, err = client.New(ctx, cfg) }","typeGuard":"func clientUsable(c *client.Client) bool { return c != nil && !c.IsClosed() }","tryCatchPattern":"users, err := cl.GetUsers(ctx, false)\nif err != nil && strings.Contains(err.Error(), \"conn was closed\") {\n    cl, err = client.New(ctx, cfg)\n    if err != nil { return trace.Wrap(err) }\n    users, err = cl.GetUsers(ctx, false)\n}","preventionTips":["Use defer cl.Close() in the same scope that creates the client so it outlives all calls.","Cancel a shared context to stop in-flight work instead of closing the client concurrently.","Recreate the client on reuse-after-idle rather than caching a closed instance."],"tags":["grpc","connection-lifecycle","client"],"backgroundTag":"grpc-connection-closed","analyzedSha":"1283425b60ec5f60d509ba4c791183d452923ff7","analyzedAt":"2026-09-02T04:06:41.601Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}