go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
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,
Claude Code
$ npx -y skills add muratmirgun/gophers --skill go-grpc --agent claude-code
Codex
$ npx skills add muratmirgun/gophers --skill go-grpc -a codex -y
Cursor
$ npx skills add muratmirgun/gophers --skill go-grpc -a cursor -y
Gemini CLI
$ npx skills add muratmirgun/gophers --skill go-grpc -a gemini-cli -y
GitHub Copilot
$ npx skills add muratmirgun/gophers --skill go-grpc -a github-copilot -y
OpenCode
$ npx skills add muratmirgun/gophers --skill go-grpc -a opencode -y
Windsurf
$ npx skills add muratmirgun/gophers --skill go-grpc -a windsurf -y
name: go-grpc description: "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." license: MIT compatibility: "Designed for Claude Code or similar AI coding agents. Requires Go 1.21+, protoc (or buf), and google.golang.org/grpc v1.60+." allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)
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`).
1. **One concern per layer.** `.proto` defines the contract; generated code lives in `gen/`; service implementation lives in `internal/`. Never edit generated files. 2. **Always wrap RPC arguments in Request/Response messages.** Bare scalars (`string`, `int32`) cannot be evolved without breaking callers. 3. **Return typed status codes, never raw errors.** A `fmt.Errorf` becomes `codes.Unknown` on the wire — the client cannot decide whether to retry. 4. **Every client call has a deadline.** No `context.Background()` to a remote service. Set `context.WithTimeout` per call. 5. **Reuse connections.** HTTP/2 multiplexes; creating a new `grpc.ClientConn` per request is a TLS handshake leak. 6. **Disable reflection in production.** Reflection is a developer convenience that doubles as an API enumeration tool for attackers.
| 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](references/proto-and-codegen.md) when organizing `.proto` packages or wiring `buf`. > Read [references/status-and-errors.md](references/status-and-errors.md) when mapping domain errors to gRPC codes.
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.
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.
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` |
| 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
}`bufconn` is an in-memory `net.Listener`. It exercises the real gRPC stack — interceptors, marshaling, metadata — without binding a TCP port. See [references/t
26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase,…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.