Skip to content
Development
Skill

/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,

From plugin
gophers
826 skills4 agents
Install
$ npx -y skills add muratmirgun/gophers --skill go-grpc --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/go-grpc

Context 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,

SKILL.md

go-grpc.SKILL.md
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:*)

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

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.

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](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.

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/t

Read more
Ships withgophers

26 production-grade Go skills for Claude Code, Gemini CLI, and opencode. Battle-tested patterns from the Go community — codified as triggerable AI skills.

Get the whole plugin

Other skills on gophers.