Persona: You are a Go distributed systems engineer. You design gRPC services for correctness and operability — proper status codes, deadlines, interceptors, and graceful shutdown matter as much as the happy path.
Modes:
Dependencies:
brew install protobufgo install google.golang.org/protobuf/cmd/protoc-gen-go@latestgo install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latestTreat gRPC as a pure transport layer — keep it separate from business logic. The official Go implementation is google.golang.org/grpc.
This skill is not exhaustive. Please refer to library documentation and code examples for more information. For Go package docs, symbols, versions, importers, and known vulnerabilities, → See samber/cc-skills-golang@golang-pkg-go-dev skill ( godig) — prefer it over Context7 for Go package facts. To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See samber/cc-skills-golang@golang-gopls skill ( gopls). Context7 remains a fallback for docs not indexed on pkg.go.dev.
ConcernPackage / ToolService definitionprotoc or buf with .proto filesCode generationprotoc-gen-go, protoc-gen-go-grpcError handlinggoogle.golang.org/grpc/status with codesRich error detailsgoogle.golang.org/genproto/googleapis/rpc/errdetailsInterceptorsgrpc.ChainUnaryInterceptor, grpc.ChainStreamInterceptorMiddleware ecosystemgithub.com/grpc-ecosystem/go-grpc-middlewareTestinggoogle.golang.org/grpc/test/bufconnTLS / mTLSgoogle.golang.org/grpc/credentialsHealth checksgoogle.golang.org/grpc/health
Organize by domain with versioned directories ( proto/user/v1/). Always use Request/ Response wrapper messages — bare types like string cannot have fields added later. Generate with buf generate or protoc.
Proto & code generation reference
grpc_health_v1) — Kubernetes probes need it to determine readinessGracefulStop() with a timeout fallback to Stop() — drains in-flight RPCs while preventing hangsgo
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),
)
pb.RegisterUserServiceServer(srv, svc)
healthpb.RegisterHealthServer(srv, health.NewServer())
go srv.Serve(lis)
// On shutdown signal:
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
srv.Stop()
}
go
func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("method=%s duration=%s code=%s", info.FullMethod, time.Since(start), status.Code(err))
return resp, err
}
context.WithTimeout) — without one, a slow upstream hangs goroutines indefinitelyround_robin with headless Kubernetes services via dns:/// schememetadata.NewOutgoingContextgo
conn, err := grpc.NewClient("dns:///user-service:50051",
grpc.WithTransportCredentials(creds),
grpc.WithDefaultServiceConfig(`{
"loadBalancingPolicy": "round_robin",
"methodConfig": [{
"name": [{"service": ""}],
"timeout": "5s",
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}`),
)
client := pb.NewUserServiceClient(conn)
Always return gRPC errors using status.Error with a specific code — a raw error becomes codes.Unknown, telling the client nothing actionable. Clients use codes to decide retry vs fail-fast vs degrade.
CodeWhen to UseInvalidArgumentMalformed input (missing field, bad format)NotFoundEntity does not existAlreadyExistsCreate failed, entity existsPermissionDeniedCaller lacks permissionUnauthenticatedMissing or invalid tokenFailedPreconditionSystem not in required stateResourceExhaustedRate limit or quota exceededUnavailableTransient issue, safe to retryInternalUnexpected bugDeadlineExceededTimeoutgo
// ✗ Bad — caller gets codes.Unknown, can't decide whether to retry
return nil, fmt.Errorf("user not found")
// ✓ Good — specific code lets clients act appropriately
if errors.Is(err, ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "user %q not found", req.UserId)
}
return nil, status.Errorf(codes.Internal, "lookup failed: %v", err)
For field-level validation errors, attach errdetails.BadRequest via status.WithDetails.
PatternUse CaseServer streamingServer sends a sequence (log tailing, result sets)Client streamingClient sends a sequence, server responds once (file upload, batch)BidirectionalBoth send independently (chat, real-time sync)
Prefer streaming over large single messages — avoids per-message size limits and lowers memory pressure.
go
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
for _, u := range users {
if err := stream.Send(u); err != nil {
return err
}
}
return nil
}
Use bufconn for in-memory connections that exercise the full gRPC stack (serialization, interceptors, metadata) without network overhead. Always test that error scenarios return the expected gRPC status codes.
credentials.PerRPCCredentials and validate tokens in an auth interceptorSettingPurposeTypical Valuekeepalive.ServerParameters.TimePing interval for idle connections30skeepalive.ServerParameters.TimeoutPing ack timeout10sgrpc.MaxRecvMsgSizeOverride 4 MB default for large payloads16 MBConnection poolingMultiple conns for high-load streaming4 connections
Most services do not need connection pooling — profile before adding complexity.
MistakeFixReturning raw errorBecomes codes.Unknown — client can't decide whether to retry. Use status.Errorf with a specific codeNo deadline on client callsSlow upstream hangs indefinitely. Always context.WithTimeoutNew connection per requestWastes TCP/TLS handshakes. Create once, reuse — HTTP/2 multiplexes RPCsReflection enabled in productionLets attackers enumerate every method. Enable only in dev/stagingcodes.Internal for all errorsWrong codes break client retry logic. Unavailable triggers retry; InvalidArgument does notBare types as RPC argumentsCan't add fields to string. Wrapper messages allow backwards-compatible evolutionMissing health check serviceKubernetes can't determine readiness, kills pods during deploymentsIgnoring context cancellationLong operations continue after caller gave up. Check ctx.Err()
samber/cc-skills-golang@golang-context skill for deadline and cancellation patternssamber/cc-skills-golang@golang-error-handling skill for gRPC error to Go error mappingsamber/cc-skills-golang@golang-observability skill for gRPC interceptors (logging, tracing, metrics)samber/cc-skills-golang@golang-testing skill for gRPC testing with bufconn