Golang Grpc
Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive framework for building robust gRPC microservices in Go. It covers the full lifecycle of gRPC development, including protobuf organization, server and client implementation patterns, error handling, security configurations, and testing strategies. It emphasizes production-grade practices such as graceful shutdowns, interceptor usage, proper status code mapping, and connection management to ensure reliable and maintainable distributed systems.
What it does
- Provides best practices for organizing protobuf files and generating Go code.
- Offers patterns for implementing gRPC servers with health checks and interceptors.
- Defines standard approaches for client-side connection management and load balancing.
- Guides developers on mapping Go errors to appropriate gRPC status codes.
- Includes techniques for testing gRPC services using in-memory bufconn connections.
- Outlines security configurations including TLS/mTLS and production hardening.
When to use it
- When designing or implementing a new gRPC service in Go.
- When auditing existing gRPC codebases for correctness and operability.
- When debugging gRPC communication issues or performance bottlenecks.
- When setting up testing suites for gRPC clients and servers.
Advantages
- Promotes consistent, production-ready gRPC architecture.
- Reduces common pitfalls like connection leaks and improper error handling.
- Facilitates easier testing of complex gRPC stacks without network overhead.
- Encourages clean separation between transport layers and business logic.
Usage examples
- “How do I implement a graceful shutdown for my gRPC server in Go?”
- The agent provides a pattern using GracefulStop() with a timeout fallback to Stop() to ensure in-flight requests are handled correctly.
- “Show me how to test my gRPC service using bufconn.”
- The agent generates a test setup using bufconn to create an in-memory connection, allowing full stack testing without network dependencies.
- “What is the best way to handle errors in my gRPC service?”
- The agent explains how to use status.Errorf with specific codes like NotFound or InvalidArgument instead of returning raw errors.
- “How should I organize my proto files for a Go project?”
- The agent recommends a domain-based, versioned directory structure (e.g., proto/user/v1/) and the use of Request/Response wrapper messages.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/samber/golang-grpc/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip golang-grpc.zip -d .agents/skills
Documentation by samber
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:
Build mode — implementing a new gRPC server or client from scratch.
Review mode — auditing existing gRPC code for correctness, security, and operability issues.
Dependencies:
protoc:
brew install protobufprotoc-gen-go:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latestprotoc-gen-go-grpc:
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
Go gRPC Best Practices
Treat 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 — 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-devskill (godig), preferred 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-goplsskill (gopls).Context7 remains a fallback for docs not indexed on pkg.go.dev.
Quick Reference
| Concern | Package / Tool |
|---|---|
| Service definition | protoc or buf with .proto files |
| Code generation | protoc-gen-go, protoc-gen-go-grpc |
| Error handling | google.golang.org/grpc/status with codes |
| Rich error details | google.golang.org/genproto/googleapis/rpc/errdetails |
| Interceptors | grpc.ChainUnaryInterceptor, grpc.ChainStreamInterceptor |
| Middleware ecosystem | github.com/grpc-ecosystem/go-grpc-middleware |
| Testing | google.golang.org/grpc/test/bufconn |
| TLS / mTLS | google.golang.org/grpc/credentials |
| Health checks | google.golang.org/grpc/health |
Proto File Organization
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
Server Implementation
Implement health check service (
grpc_health_v1) — Kubernetes probes need it to determine readinessUse interceptors for cross-cutting concerns (logging, auth, recovery) — keeps business logic clean
Use
GracefulStop()with a timeout fallback toStop()— drains in-flight RPCs while preventing hangsDisable reflection in production — it exposes your full API surface
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()
}Interceptor Pattern
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
}Client Implementation
Reuse connections — gRPC multiplexes RPCs on a single HTTP/2 connection; one-per-request wastes TCP/TLS handshakes
Set deadlines on every call (
context.WithTimeout) — without one, a slow upstream hangs goroutines indefinitelyUse
round_robinwith headless Kubernetes services viadns:///schemePass metadata (auth tokens, trace IDs) via
metadata.NewOutgoingContext
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)Error Handling
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.
| Code | When to Use |
|---|---|
InvalidArgument | Malformed input (missing field, bad format) |
NotFound | Entity does not exist |
AlreadyExists | Create failed, entity exists |
PermissionDenied | Caller lacks permission |
Unauthenticated | Missing or invalid token |
FailedPrecondition | System not in required state |
ResourceExhausted | Rate limit or quota exceeded |
Unavailable | Transient issue, safe to retry |
Internal | Unexpected bug |
DeadlineExceeded | Timeout |
// ✗ 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.
Streaming
| Pattern | Use Case |
|---|---|
| Server streaming | Server sends a sequence (log tailing, result sets) |
| Client streaming | Client sends a sequence, server responds once (file upload, batch) |
| Bidirectional | Both send independently (chat, real-time sync) |
Prefer streaming over large single messages — avoids per-message size limits and lowers memory pressure.
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
}Testing
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.
Security
TLS MUST be enabled in production — credentials travel in metadata
For service-to-service auth, use mTLS or delegate to a service mesh (Istio, Linkerd)
For user auth, implement
credentials.PerRPCCredentialsand validate tokens in an auth interceptorReflection SHOULD be disabled in production to prevent API discovery
Performance
| Setting | Purpose | Typical Value |
|---|---|---|
keepalive.ServerParameters.Time | Ping interval for idle connections | 30s |
keepalive.ServerParameters.Timeout | Ping ack timeout | 10s |
grpc.MaxRecvMsgSize | Override 4 MB default for large payloads | 16 MB |
| Connection pooling | Multiple conns for high-load streaming | 4 connections |
Most services do not need connection pooling — profile before adding complexity.
Common Mistakes
| Mistake | Fix |
|---|---|
Returning raw error | Becomes codes.Unknown — client can't decide whether to retry. Use status.Errorf with a specific code |
| No deadline on client calls | Slow upstream hangs indefinitely. Always context.WithTimeout |
| New connection per request | Wastes TCP/TLS handshakes. Create once, reuse — HTTP/2 multiplexes RPCs |
| Reflection enabled in production | Lets attackers enumerate every method. Enable only in dev/staging |
codes.Internal for all errors | Wrong codes break client retry logic. Unavailable triggers retry; InvalidArgument does not |
| Bare types as RPC arguments | Can't add fields to string. Wrapper messages allow backwards-compatible evolution |
| Missing health check service | Kubernetes can't determine readiness, kills pods during deployments |
| Ignoring context cancellation | Long operations continue after caller gave up. Check ctx.Err() |
Cross-References
→ See
samber/cc-skills-golang@golang-contextskill for deadline and cancellation patterns→ See
samber/cc-skills-golang@golang-error-handlingskill for gRPC error to Go error mapping→ See
samber/cc-skills-golang@golang-observabilityskill for gRPC interceptors (logging, tracing, metrics)→ See
samber/cc-skills-golang@golang-testingskill for gRPC testing with bufconn
Files
- ATTRIBUTION.mdAdded by AutoGineer890 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md9.9 KiB
- evals/evals.json12.3 KiB
- references/protoc-reference.md4.6 KiB
- references/testing.md7.6 KiB
evals/evals.json
[
{
"id": 1,
"name": "raw-error-vs-status-error",
"description": "Tests that gRPC handlers return status.Errorf with specific codes, not raw errors",
"prompt": "Write a Go gRPC handler for GetUser that looks up a user from a repository. If the user is not found, return an appropriate error. If the repository returns an unexpected error, return that too.",
"trap": "Without the skill, the model may return fmt.Errorf or raw errors, which become codes.Unknown. The skill requires status.Errorf with specific codes.",
"assertions": [
{
"id": "1.1",
"text": "Uses status.Errorf (or status.Error) for the not-found case with codes.NotFound"
},
{
"id": "1.2",
"text": "Uses status.Errorf with codes.Internal for unexpected errors"
},
{
"id": "1.3",
"text": "Does NOT return a raw fmt.Errorf or errors.New as the gRPC error"
},
{
"id": "1.4",
"text": "Imports google.golang.org/grpc/status and google.golang.org/grpc/codes"
},
{
"id": "1.5",
"text": "Does NOT leak internal error details in the user-facing gRPC message for Internal errors"
}
]
},
{
"id": 2,
"name": "wrapper-messages-not-bare-types",
"description": "Tests that proto RPCs use Request/Response wrapper messages, not bare types",
"prompt": "Write a proto3 service definition for a product catalog with RPCs: GetProduct (takes product ID, returns product), DeleteProduct (takes product ID, returns nothing), SearchProducts (takes search query string, returns list of products).",
"trap": "Without the skill, the model may use bare types like string or google.protobuf.Empty. The skill requires Request/Response wrappers for backward-compatible evolution.",
"assertions": [
{
"id": "2.1",
"text": "Uses GetProductRequest/GetProductResponse wrapper messages, not bare string or Product"
},
{
"id": "2.2",
"text": "Uses DeleteProductRequest/DeleteProductResponse wrapper messages, not bare string or google.protobuf.Empty"
},
{
"id": "2.3",
"text": "Uses SearchProductsRequest/SearchProductsResponse wrapper messages"
},
{
"id": "2.4",
"text": "Each wrapper message has properly named fields (not just a single unnamed field)"
},
{ "id": "2.5", "text": "Includes go_package option in the proto file" }
]
},
{
"id": 3,
"name": "proto-directory-organization",
"description": "Tests proper proto file organization by domain with versioned directories",
"prompt": "I'm building a microservice with user management and order management. How should I organize my proto files? Show the directory layout and an example buf.gen.yaml.",
"trap": "Without the skill, the model may put all protos in a flat directory or skip versioning. The skill requires domain-based organization with v1 directories.",
"assertions": [
{
"id": "3.1",
"text": "Organizes proto files by domain (user/, order/ or similar domain grouping)"
},
{
"id": "3.2",
"text": "Includes version directories (v1/ under each domain)"
},
{
"id": "3.3",
"text": "Separates message definitions from service definitions (e.g. user.proto vs user_service.proto)"
},
{
"id": "3.4",
"text": "Includes a shared/ or common/ directory for shared messages"
},
{
"id": "3.5",
"text": "Shows buf.gen.yaml with go and go-grpc plugins configured"
}
]
},
{
"id": 4,
"name": "graceful-stop-with-timeout-fallback",
"description": "Tests proper gRPC server shutdown: GracefulStop with timeout fallback to Stop",
"prompt": "Write Go code for a gRPC server that handles shutdown signals properly. The server should try to drain in-flight requests before stopping.",
"trap": "Without the skill, the model may only call srv.Stop() or only srv.GracefulStop() without a timeout fallback. The skill requires GracefulStop with a timeout fallback to Stop.",
"assertions": [
{
"id": "4.1",
"text": "Calls srv.GracefulStop() first to drain in-flight RPCs"
},
{
"id": "4.2",
"text": "Has a timeout mechanism that falls back to srv.Stop() if GracefulStop takes too long"
},
{
"id": "4.3",
"text": "Uses a select statement or timer for the timeout"
},
{
"id": "4.4",
"text": "Listens for OS signals (SIGINT, SIGTERM or os.Interrupt)"
},
{ "id": "4.5", "text": "The timeout is reasonable (5-30 seconds)" }
]
},
{
"id": 5,
"name": "health-check-service-registration",
"description": "Tests that gRPC servers register the health check service for Kubernetes readiness probes",
"prompt": "Write a production-ready gRPC server setup in Go. Register a UserService and make it deployable to Kubernetes.",
"trap": "Without the skill, the model may not register the health check service. The skill says health check is required for Kubernetes probes.",
"assertions": [
{
"id": "5.1",
"text": "Registers grpc_health_v1.RegisterHealthServer (or equivalent health check service)"
},
{
"id": "5.2",
"text": "Uses health.NewServer() to create the health service"
},
{
"id": "5.3",
"text": "Registers interceptors using grpc.ChainUnaryInterceptor"
},
{
"id": "5.4",
"text": "Does NOT enable reflection (or explicitly disables it for production)"
},
{ "id": "5.5", "text": "Includes graceful shutdown handling" }
]
},
{
"id": 6,
"name": "client-connection-reuse-and-deadlines",
"description": "Tests that gRPC clients reuse connections and set deadlines on every call",
"prompt": "Write a Go gRPC client that calls a UserService. Create the client and make a GetUser call.",
"trap": "Without the skill, the model may create a new connection per request or not set a deadline. The skill requires connection reuse and context.WithTimeout on every call.",
"assertions": [
{
"id": "6.1",
"text": "Creates the connection once and reuses it (not creating a new connection per call)"
},
{
"id": "6.2",
"text": "Uses context.WithTimeout (or context.WithDeadline) for the RPC call"
},
{
"id": "6.3",
"text": "Uses grpc.NewClient (not the deprecated grpc.Dial)"
},
{
"id": "6.4",
"text": "Includes transport credentials (TLS or explicitly insecure for dev)"
},
{
"id": "6.5",
"text": "Properly defers cancel() from context.WithTimeout"
}
]
},
{
"id": 7,
"name": "bufconn-testing-pattern",
"description": "Tests that gRPC tests use bufconn for in-memory connections, not real network",
"prompt": "Write a test for a Go gRPC UserService that verifies GetUser returns the correct user and that requesting a non-existent user returns the proper error code.",
"trap": "Without the skill, the model may start a real TCP server for testing. The skill requires bufconn for in-memory connections.",
"assertions": [
{ "id": "7.1", "text": "Uses bufconn.Listen for an in-memory listener" },
{
"id": "7.2",
"text": "Uses grpc.WithContextDialer with the bufconn dialer"
},
{
"id": "7.3",
"text": "Verifies the gRPC status code for the not-found case using status.FromError"
},
{
"id": "7.4",
"text": "Checks that the error code is codes.NotFound specifically"
},
{
"id": "7.5",
"text": "Uses t.Cleanup or defer for cleaning up the server and connection"
}
]
},
{
"id": 8,
"name": "error-code-selection-judgment",
"description": "Tests correct gRPC error code selection across different scenarios",
"prompt": "Write a Go gRPC handler for CreateOrder that validates the request has a non-empty user_id and at least one item, checks the user exists, checks inventory availability, and creates the order. Return appropriate gRPC errors for each failure case.",
"trap": "Without the skill, the model may use codes.Internal for everything or pick wrong codes. The skill has a specific mapping table.",
"assertions": [
{
"id": "8.1",
"text": "Uses codes.InvalidArgument for missing user_id or empty items list"
},
{ "id": "8.2", "text": "Uses codes.NotFound for user not found" },
{
"id": "8.3",
"text": "Uses codes.FailedPrecondition for insufficient inventory (system not in required state)"
},
{
"id": "8.4",
"text": "Uses codes.Internal only for truly unexpected errors"
},
{
"id": "8.5",
"text": "Does NOT use codes.Unknown for any handled error case"
}
]
},
{
"id": 9,
"name": "unimplemented-server-embedding",
"description": "Tests that gRPC server implementations embed UnimplementedXxxServer, not UnsafeXxxServer",
"prompt": "Write a Go implementation of a gRPC server for an OrderService with CreateOrder and GetOrder RPCs. Show the struct definition and method signatures.",
"trap": "Without the skill, the model may embed UnsafeXxxServer or not embed any base server. The skill requires UnimplementedXxxServer for forward compatibility.",
"assertions": [
{
"id": "9.1",
"text": "Embeds UnimplementedOrderServiceServer in the server struct"
},
{ "id": "9.2", "text": "Does NOT embed UnsafeOrderServiceServer" },
{
"id": "9.3",
"text": "Implements the service methods with correct signatures (context, request pointer, response pointer and error return)"
},
{
"id": "9.4",
"text": "Uses status.Errorf for error returns in the handler methods"
},
{
"id": "9.5",
"text": "Registers the server with pb.RegisterOrderServiceServer"
}
]
},
{
"id": 10,
"name": "client-load-balancing-and-retry",
"description": "Tests knowledge of gRPC client-side load balancing and retry configuration",
"prompt": "I'm deploying a Go gRPC client in Kubernetes. The server has multiple replicas behind a headless service. How do I configure the client to distribute requests across all replicas and retry on transient failures?",
"trap": "Without the skill, the model may suggest a separate load balancer or basic round-robin without the dns:/// scheme. The skill shows the specific configuration.",
"assertions": [
{
"id": "10.1",
"text": "Uses the dns:/// scheme for service discovery with headless Kubernetes services"
},
{
"id": "10.2",
"text": "Configures round_robin load balancing policy via service config"
},
{
"id": "10.3",
"text": "Configures retry policy in the service config with retryableStatusCodes containing UNAVAILABLE"
},
{
"id": "10.4",
"text": "Sets maxAttempts, initialBackoff, maxBackoff in the retry policy"
},
{
"id": "10.5",
"text": "Does NOT suggest creating a new connection per request for load distribution"
}
]
},
{
"id": 11,
"name": "streaming-over-large-messages",
"description": "Tests preference for streaming over large single messages",
"prompt": "I need to send a result set of 50,000 records from a gRPC server to a client. Each record is about 1KB. What's the best approach?",
"trap": "Without the skill, the model may suggest increasing MaxRecvMsgSize and sending all records in one response. The skill recommends streaming to avoid per-message size limits and memory pressure.",
"assertions": [
{
"id": "11.1",
"text": "Recommends server streaming RPC to send records incrementally"
},
{
"id": "11.2",
"text": "Explains why a single large message is problematic (size limits, memory pressure)"
},
{
"id": "11.3",
"text": "Shows the server streaming pattern with stream.Send in a loop"
},
{
"id": "11.4",
"text": "Client reads with stream.Recv in a loop, checking for io.EOF"
},
{
"id": "11.5",
"text": "Does NOT just increase MaxRecvMsgSize as the primary solution"
}
]
}
]
Requirements and compatibility
Written for: Claude Code, OpenAI Codex
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Declares allowed tools, Claude Code frontmatter
The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- samber
- Publisher
- Samuel Berthe (Community)
- Distributed by
- Samuel Berthe
- Source
- samber/cc-skills-golang · skills/golang-grpc · commit bb1d43d
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-grpc.zip