{"record":{"id":"12055abafda736ab","repo":"grpc/grpc-go","slug":"credentials-rawconn-is-dispatched-out-of-grpc","errorCode":null,"errorMessage":"credentials: rawConn is dispatched out of gRPC","messagePattern":"credentials: rawConn is dispatched out of gRPC","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"info","filePath":"credentials/credentials.go","lineNumber":148,"sourceCode":"}\n\n// AuthorityValidator validates the authority used to override the `:authority`\n// header. This is an optional interface that implementations of AuthInfo can\n// implement if they support per-RPC authority overrides. It is invoked when the\n// application attempts to override the HTTP/2 `:authority` header using the\n// CallAuthority call option.\ntype AuthorityValidator interface {\n\t// ValidateAuthority checks the authority value used to override the\n\t// `:authority` header. The authority parameter is the override value\n\t// provided by the application via the CallAuthority option. This value\n\t// typically corresponds to the server hostname or endpoint the RPC is\n\t// targeting. It returns non-nil error if the validation fails.\n\tValidateAuthority(authority string) error\n}\n\n// ErrConnDispatched indicates that rawConn has been dispatched out of gRPC\n// and the caller should not close rawConn.\nvar ErrConnDispatched = errors.New(\"credentials: rawConn is dispatched out of gRPC\")\n\n// TransportCredentials defines the common interface for all the live gRPC wire\n// protocols and supported transport security protocols (e.g., TLS, SSL).\ntype TransportCredentials interface {\n\t// ClientHandshake does the authentication handshake specified by the\n\t// corresponding authentication protocol on rawConn for clients. It returns\n\t// the authenticated connection and the corresponding auth information\n\t// about the connection.  The auth information should embed CommonAuthInfo\n\t// to return additional information about the credentials. Implementations\n\t// must use the provided context to implement timely cancellation.  gRPC\n\t// will try to reconnect if the error returned is a temporary error\n\t// (io.EOF, context.DeadlineExceeded or err.Temporary() == true).  If the\n\t// returned error is a wrapper error, implementations should make sure that\n\t// the error implements Temporary() to have the correct retry behaviors.\n\t// Additionally, ClientHandshakeInfo data will be available via the context\n\t// passed to this call.\n\t//\n\t// The second argument to this method is the `:authority` header value used","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/credentials.go#L130-L166","documentation":"ErrConnDispatched (credentials/credentials.go:146-148) is a sentinel that a custom TransportCredentials implementation returns to signal that the rawConn has been handed off out of gRPC and the caller MUST NOT close it. It is not a failure — it is a control-flow signal used by proxy/forwarding credentials (e.g. http_proxy credentials) that take ownership of the underlying net.Conn.","triggerScenarios":"A custom ClientHandshake/ServerHandshake returns (nil, nil, credentials.ErrConnDispatched) after forwarding/spawning a listener on rawConn. gRPC's transport code treats this sentinel specially to skip closing the connection it no longer owns.","commonSituations":"Implementing proxy-connect or in-process listener credentials (e.g. the gRPC xDS/proxy code paths); test doubles that move the conn; an accidentally-returned ErrConnDispatched from a buggy custom cred that then leaks the conn.","solutions":["Treat ErrConnDispatched as a signal, not an error: the conn is now owned elsewhere — do not log/fail on it.","When implementing such a credential, ensure you actually transfer ownership (start a reader/writer goroutine) before returning the sentinel, or you will leak the conn.","If you did not intend to dispatch, return the real handshake error instead so gRPC closes rawConn.","Unit-test that your credential returns this sentinel only on the intended code path."],"exampleFix":"// before — custom cred hands off conn but returns a generic error (conn gets closed)\nfunc (c *proxyCreds) ClientHandshake(ctx context.Context, a string, raw net.Conn) (net.Conn, credentials.AuthInfo, error) {\n    go forward(raw)\n    return nil, nil, errors.New(\"dispatched\") // gRPC closes raw -> broken\n}\n\n// after — return the sentinel so gRPC leaves rawConn alone\nfunc (c *proxyCreds) ClientHandshake(ctx context.Context, a string, raw net.Conn) (net.Conn, credentials.AuthInfo, error) {\n    go forward(raw)\n    return nil, nil, credentials.ErrConnDispatched\n}","handlingStrategy":"try-catch","validationCode":"// When consuming a custom credential that may dispatch the conn,\n// treat the sentinel as a signal, not an error\nfunc dialWithProxy(target string) error {\n    _, _, err := creds.ClientHandshake(ctx, target, raw)\n    if errors.Is(err, credentials.ErrConnDispatched) {\n        return nil // conn ownership transferred; success\n    }\n    return err\n}","typeGuard":"func isConnDispatched(err error) bool {\n    return errors.Is(err, credentials.ErrConnDispatched)\n}","tryCatchPattern":"if errors.Is(err, credentials.ErrConnDispatched) {\n    // not a failure: rawConn is owned elsewhere; do not close it\n}","preventionTips":["Recognize ErrConnDispatched as a control-flow signal, not an error.","Only return it after truly transferring conn ownership (else you leak).","Return the real error otherwise so gRPC closes rawConn."],"tags":["credentials","transport","sentinel","grpc-go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}