Go grpc
Use when implementing or reviewing gRPC servers/clients in Go. Covers .proto organisation, code generation with protoc/buf, server bootstrap (interceptors, health, graceful shutdown), client patterns (reuse, deadlines, retries), status.Code error handling, streaming, TLS/mTLS, and bufconn testing. Apply when writing .proto files, adding interceptors, or auditing a service for production readiness.From its SKILL.md
npx -y skills add muratmirgun/gophers --skill go-grpcAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
8.4 KB, as published. Nobody here has run it
Go gRPC
Treat gRPC as a transport. Keep .proto-generated code and business logic separated. The official Go implementation is google.golang.org/grpc; pair it with protoc-gen-go + protoc-gen-go-grpc (or buf generate).
Core Rules
- One concern per layer.
.protodefines the contract; generated code lives ingen/; service implementation lives ininternal/. Never edit generated files. - Always wrap RPC arguments in Request/Response messages. Bare scalars (
string,int32) cannot be evolved without breaking callers. - Return typed status codes, never raw errors. A
fmt.Errorfbecomescodes.Unknownon the wire — the client cannot decide whether to retry. - Every client call has a deadline. No
context.Background()to a remote service. Setcontext.WithTimeoutper call. - Reuse connections. HTTP/2 multiplexes; creating a new
grpc.ClientConnper request is a TLS handshake leak. - Disable reflection in production. Reflection is a developer convenience that doubles as an API enumeration tool for attackers.
When to Use What
| Need | Use |
|---|---|
| Define service | .proto file in proto/<service>/v1/ |
| Generate stubs | buf generate or protoc --go_out --go-grpc_out |
| Cross-cutting (auth, logging, recovery) | grpc.ChainUnaryInterceptor / ChainStreamInterceptor |
| Health probes (Kubernetes) | grpc_health_v1 from google.golang.org/grpc/health |
| Errors with details | status.Errorf(codes.X, ...) + WithDetails(errdetails.BadRequest{...}) |
| Tests | google.golang.org/grpc/test/bufconn |
| Service mesh / mTLS | credentials.NewTLS or delegate to Istio/Linkerd |
Read references/proto-and-codegen.md when organizing
.protopackages or wiringbuf. Read references/status-and-errors.md when mapping domain errors to gRPC codes.
Server Bootstrap
import (
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
)
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(recoveryUnary, loggingUnary, authUnary),
grpc.ChainStreamInterceptor(recoveryStream, loggingStream),
)
pb.RegisterUserServiceServer(srv, &userService{...})
healthpb.RegisterHealthServer(srv, health.NewServer())
go func() { _ = srv.Serve(lis) }()
// Graceful shutdown bounded by a hard timeout.
<-shutdownSignal
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped:
case <-time.After(15 * time.Second):
srv.Stop()
}
Three pieces are non-negotiable: interceptors for cross-cutting concerns, health service for Kubernetes probes, and a bounded graceful shutdown.
Client Bootstrap
conn, _ := grpc.NewClient("dns:///user-service:50051",
grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)),
grpc.WithDefaultServiceConfig(`{
"loadBalancingPolicy": "round_robin",
"methodConfig": [{
"name": [{"service": "user.v1.UserService"}],
"timeout": "5s",
"retryPolicy": {
"maxAttempts": 3, "initialBackoff": "0.1s", "maxBackoff": "1s",
"backoffMultiplier": 2, "retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}`),
)
client := pb.NewUserServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second); defer cancel()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: id})
The service config is the right place for retries — let the library handle the loop, backoff, and UNAVAILABLE-only filter.
Errors
A raw Go error returned from an RPC becomes codes.Unknown. The client cannot tell a 404 from a 500. Always use status.Errorf:
if errors.Is(err, ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "user %q not found", req.Id)
}
if errors.As(err, &validationErr) {
st, _ := status.New(codes.InvalidArgument, "validation").WithDetails(
&errdetails.BadRequest{FieldViolations: violations(validationErr)},
)
return nil, st.Err()
}
return nil, status.Errorf(codes.Internal, "lookup: %v", err)
Quick map:
| Domain | Code |
|---|---|
| Missing/invalid field | InvalidArgument |
| Not found | NotFound |
| Already exists | AlreadyExists |
| Unauthenticated | Unauthenticated |
| Authenticated but forbidden | PermissionDenied |
| Rate-limited | ResourceExhausted |
| Dependency down, retriable | Unavailable |
| Bug, unexpected | Internal |
Streaming
| Pattern | Use case |
|---|---|
| Server streaming | Log tailing, paginated result sets, server-sent events |
| Client streaming | File upload, batch ingest |
| Bidirectional | Chat, real-time sync |
Streams must respect ctx.Done(). A goroutine reading from a stream after the client disconnects is a slow leak.
func (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
for _, u := range s.repo.All(stream.Context()) {
if err := stream.Send(toProto(u)); err != nil {
return err // includes ctx canceled
}
}
return nil
}
Testing with bufconn
bufconn is an in-memory net.Listener. It exercises the real gRPC stack — interceptors, marshaling, metadata — without binding a TCP port. See references/testing.md for the full harness plus table-driven status-code assertions, metadata injection, and stream testing.
Security Notes
- TLS in production. Plaintext is only acceptable behind a confirmed-private network (and even then mTLS is preferable).
- For service-to-service auth, prefer a mesh (Istio/Linkerd) over hand-rolled token validation.
- For user auth, implement
credentials.PerRPCCredentialsto attach a token and validate inside an auth interceptor. - Reflection: enable in dev, disable in prod via build tag or env flag.
Anti-Patterns
| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
return fmt.Errorf("not found") | Wire code is Unknown, clients can't retry-discriminate | status.Errorf(codes.NotFound, ...) |
context.Background() to a client call | No deadline → goroutines pile up on a slow dependency | context.WithTimeout(parent, 5s) |
New ClientConn per request | TLS handshake every call; sockets exhaust | One grpc.NewClient at startup, reuse |
Bare string as RPC argument | Cannot add fields without breaking callers | Always Request/Response messages |
| Reflection on in production | Lets attackers enumerate every method | Compile-out with build tag in prod |
codes.Internal for all errors | Client retry config can't distinguish bugs from outages | Map domain → specific codes |
| No health service | Kubernetes can't gate traffic; rolling deploys break | Register grpc_health_v1 |
Ignoring stream.Context().Done() | Goroutines run after client disconnect | Select on ctx.Done() in stream loops |
Verification Checklist
-
.protopackages are versioned (pkg/v1, notpkg) - All RPCs take Request and return Response messages
- Generated code is in a separate directory, never edited
- Every error return uses
status.Errorfwith a specific code - Every client call has a deadline via
context.WithTimeout - Server registers
grpc_health_v1 -
GracefulStopis bounded by atime.Afterfallback - Reflection is gated to non-production builds
- Tests use
bufconnand assertstatus.Code(err)
References
- references/proto-and-codegen.md —
.protolayout,buf.yaml, codegen flags - references/status-and-errors.md — code mapping, rich details with
errdetails - references/testing.md —
bufconn, metadata, streaming assertions - references/anti-patterns.md — detailed walkthrough of each anti-pattern
What ships with it: 4 files
15.3 KB alongside SKILL.md
references/
- anti-patterns.md3.9 KB
- proto-and-codegen.md3.8 KB
- status-and-errors.md4.1 KB
- testing.md3.5 KB
Gives 0 of the 12 instructions most apis services skills give
Counted across 448 of the 471 authors here whose files we hold, read 2026-09-06
- Use HTTP status codes semanticallyin 25 of 448, across 11 files
- Return 201 with a Location header on createin 24 of 448, across 9 files
- Name resources plural, lowercase, kebab-casein 23 of 448, across 9 files
- Configure rate limiting with limit headersin 22 of 448, across 8 files
- Paginate list endpoints with cursor or offsetin 21 of 448, across 10 files
- Version APIs in the URL pathin 21 of 448, across 11 files
- Validate request input with a schemain 21 of 448, across 7 files
- Add pagination to all list endpointsin 18 of 448, across 15 files
- Match HTTP method to the operationin 12 of 448, across 6 files
- Return 400 or 422 with field-level detailsin 12 of 448, across 2 files
- Check ownership before returning resourcesin 12 of 448, across 2 files
- Limit query depth and complexityin 12 of 448, across 7 files
Said here and by no other author read
- Separate generated code from business logic
- Wrap RPC arguments in request and response messages
- Return specific status codes instead of raw errors
- Reuse one client connection across requests
- Disable reflection in production
- Register the gRPC health service
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.