{"record":{"id":"a66a63d0e47ef88c","repo":"grpc/grpc-go","slug":"the-tap-handle-was-already-set-and-may-not-be-rese","errorCode":null,"errorMessage":"The tap handle was already set and may not be reset.","messagePattern":"The tap handle was already set and may not be reset\\.","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"server.go","lineNumber":535,"sourceCode":"// while the last interceptor will be the inner most wrapper around the real call.\n// All stream interceptors added by this method will be chained.\nfunc ChainStreamInterceptor(interceptors ...StreamServerInterceptor) ServerOption {\n\treturn newFuncServerOption(func(o *serverOptions) {\n\t\to.chainStreamInts = append(o.chainStreamInts, interceptors...)\n\t})\n}\n\n// InTapHandle returns a ServerOption that sets the tap handle for all the server\n// transport to be created. Only one can be installed.\n//\n// # Experimental\n//\n// Notice: This API is EXPERIMENTAL and may be changed or removed in a\n// later release.\nfunc InTapHandle(h tap.ServerInHandle) ServerOption {\n\treturn newFuncServerOption(func(o *serverOptions) {\n\t\tif o.inTapHandle != nil {\n\t\t\tpanic(\"The tap handle was already set and may not be reset.\")\n\t\t}\n\t\to.inTapHandle = h\n\t})\n}\n\n// StatsHandler returns a ServerOption that sets the stats handler for the server.\nfunc StatsHandler(h stats.Handler) ServerOption {\n\treturn newFuncServerOption(func(o *serverOptions) {\n\t\tif h == nil {\n\t\t\tlogger.Error(\"ignoring nil parameter in grpc.StatsHandler ServerOption\")\n\t\t\t// Do not allow a nil stats handler, which would otherwise cause\n\t\t\t// panics.\n\t\t\treturn\n\t\t}\n\t\to.statsHandlers = append(o.statsHandlers, h)\n\t})\n}\n","sourceCodeStart":517,"sourceCodeEnd":553,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/server.go#L517-L553","documentation":"grpc.InTapHandle (server.go:532) installs a single tap handle (tap.ServerInHandle) that can inspect/abort incoming RPCs at the transport level. Only one may be installed, so it panics if o.inTapHandle is already set. This is an EXPERIMENTAL API applied at NewServer time.","triggerScenarios":"Calling grpc.InTapHandle(h) twice in the options passed to NewServer, or combining an option slice that already carries an InTapHandle with another one.","commonSituations":"A shared server-setup helper installs a tap handle and application code adds its own; two libraries both register a tap handle; migrating configuration without removing the prior registration.","solutions":["Ensure grpc.InTapHandle appears at most once across all options passed to NewServer.","If you need multiple inspection points, compose them into a single tap handle that fans out internally.","Remove the duplicate from whichever option slice is being merged."],"exampleFix":"// before\nsrv := grpc.NewServer(\n    grpc.InTapHandle(tapA),\n    grpc.InTapHandle(tapB), // panics: already set\n)\n\n// after: compose into one handle\ncombined := &fanoutTap{handles: []tap.ServerInHandle{tapA, tapB}}\nsrv := grpc.NewServer(grpc.InTapHandle(combined))","handlingStrategy":"validation","validationCode":"// Ensure at most one InTapHandle across option sources\nvar tapInstalled bool\nfor _, o := range opts {\n    if isTapOption(o) { tapInstalled = true } // requires your own tagging\n}\nif !tapInstalled && myHandle != nil {\n    opts = append(opts, grpc.InTapHandle(myHandle))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat InTapHandle as a singleton; if two components need tap inspection, compose them into one handle.","Confirm no shared library registers an InTapHandle before adding your own.","Remember InTapHandle is EXPERIMENTAL; prefer StatsHandler where possible."],"tags":["server","tap","panic","grpc-go","config","experimental"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}