go-clean-architecture
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward…
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,
$ npx -y skills add muratmirgun/gophers --skill go-grpc --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/go-grpcContext preview
The summary Claude sees to decide when to auto-load this skill.
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,
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.
Repo: muratmirgun/gophers
Use when scaffolding or refactoring a Go service into a framework-agnostic clean (hexagonal) architecture: Domain, Usecase, Repository, Delivery layers, inward…
Invoke this skill to systematically review a Go change against community style standards before merging. Walks the diff topic by topic — formatting, errors,…
Use when writing or reviewing Go code for clarity, formatting, control flow, variable declarations, switch usage, and function design. Covers the priority…
Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker pools, or fan-out/fan-in…
Use when designing, propagating, or debugging context.Context flow in Go — first-parameter placement, deadlines and cancellation, request-scoped values,…
Use when writing conditionals, loops, switches, type switches, or blank-identifier patterns in Go. Covers if-with-initialization, guard clauses, early returns,…