{"record":{"id":"bc8df2e38a2ff546","repo":"grpc/grpc-go","slug":"the-unary-server-interceptor-was-already-set-and-m","errorCode":null,"errorMessage":"The unary server interceptor was already set and may not be reset.","messagePattern":"The unary server interceptor was already set and may not be reset\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"server.go","lineNumber":488,"sourceCode":"\treturn newFuncServerOption(func(o *serverOptions) {\n\t\to.maxConcurrentStreams = n\n\t})\n}\n\n// Creds returns a ServerOption that sets credentials for server connections.\nfunc Creds(c credentials.TransportCredentials) ServerOption {\n\treturn newFuncServerOption(func(o *serverOptions) {\n\t\to.creds = c\n\t})\n}\n\n// UnaryInterceptor returns a ServerOption that sets the UnaryServerInterceptor for the\n// server. Only one unary interceptor can be installed. The construction of multiple\n// interceptors (e.g., chaining) can be implemented at the caller.\nfunc UnaryInterceptor(i UnaryServerInterceptor) ServerOption {\n\treturn newFuncServerOption(func(o *serverOptions) {\n\t\tif o.unaryInt != nil {\n\t\t\tpanic(\"The unary server interceptor was already set and may not be reset.\")\n\t\t}\n\t\to.unaryInt = i\n\t})\n}\n\n// ChainUnaryInterceptor returns a ServerOption that specifies the chained interceptor\n// for unary RPCs. The first interceptor will be the outer most,\n// while the last interceptor will be the inner most wrapper around the real call.\n// All unary interceptors added by this method will be chained.\nfunc ChainUnaryInterceptor(interceptors ...UnaryServerInterceptor) ServerOption {\n\treturn newFuncServerOption(func(o *serverOptions) {\n\t\to.chainUnaryInts = append(o.chainUnaryInts, interceptors...)\n\t})\n}\n\n// StreamInterceptor returns a ServerOption that sets the StreamServerInterceptor for the\n// server. Only one stream interceptor can be installed.\nfunc StreamInterceptor(i StreamServerInterceptor) ServerOption {","sourceCodeStart":470,"sourceCodeEnd":506,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/server.go#L470-L506","documentation":"grpc.UnaryInterceptor (server.go:485) installs a single non-chained unary server interceptor. gRPC forbids setting it twice because a second call would silently overwrite the first (a security/observability footgun), so it panics if o.unaryInt is already non-nil. This option is applied during server construction, so the panic happens at NewServer time.","triggerScenarios":"Passing grpc.UnaryInterceptor(...) twice in the same NewServer(opts...) call, or a helper that bundles a UnaryInterceptor option being combined with an explicit UnaryInterceptor at the call site, so that o.unaryInt is set on the second invocation.","commonSituations":"A shared option list already contains a unary interceptor (e.g. from a framework) and application code adds another; migrating from single to chained interceptors but forgetting to remove the old UnaryInterceptor call; merging two option slices that each carry one.","solutions":["Replace multiple grpc.UnaryInterceptor calls with a single grpc.ChainUnaryInterceptor(a, b, ...) which supports many interceptors.","Search the combined option slice passed to NewServer for duplicate UnaryInterceptor entries and keep only one.","If a framework injects one, disable its interceptor or use its chaining hook instead of adding your own UnaryInterceptor."],"exampleFix":"// before\nsrv := grpc.NewServer(\n    grpc.UnaryInterceptor(loggingInterceptor),\n    grpc.UnaryInterceptor(authInterceptor), // panics: already set\n)\n\n// after\nsrv := grpc.NewServer(\n    grpc.ChainUnaryInterceptor(loggingInterceptor, authInterceptor),\n)","handlingStrategy":"validation","validationCode":"// Detect duplicate UnaryInterceptor in assembled options before NewServer\nfunc hasUnaryInterceptor(opts []grpc.ServerOption) bool {\n    // grpc.ServerOption is opaque; instead, manage interceptors through a\n    // single ChainUnaryInterceptor so duplicates are structurally impossible.\n    return false\n}\n// Prefer: collect interceptors in a slice, register once via ChainUnaryInterceptor.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Standardize on ChainUnaryInterceptor everywhere; ban raw UnaryInterceptor in shared builders.","Keep a single place that assembles server options to avoid merging two slices that each carry an interceptor.","Document that single-interceptor options are one-shot."],"tags":["server","interceptor","panic","grpc-go","config"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}