go-grpc — скилл для Claude Code: gRPC-сервисы на Go
Скилл для ИИ-агента, который ведёт разработку gRPC-сервисов на Go: proto-контракты, коды ошибок, дедлайны, интерцепторы и стриминг.
npx skills add eduardo-sl/go-agent-skills --skill go-grpc -a claude-codeЧто делает go-grpc
Скилл даёт агенту набор правил для gRPC в Go. Версия пакета ставится в имя (payment.v1), теги полей не переиспользуются, под каждый RPC заводится своя пара Request/Response. Сгенерированный код коммитится, а buf lint и buf breaking в CI ловят переименованные поля до того, как их увидит клиент.
Ошибки возвращаются через status.Error с маппингом доменных ошибок в одном месте: NotFound, AlreadyExists, InvalidArgument против FailedPrecondition, Unavailable для повторов. Клиент разбирает код через status.FromError, а не текст сообщения. Дедлайн ставится на каждый вызов, и ctx передаётся дальше в БД и другие RPC — иначе отмена не дойдёт до конца цепочки.
Кросс-заботы выносятся в интерцепторы: recovery снаружи, потом логирование, потом авторизация. Для стриминга есть параллельные StreamInterceptor. Тесты идут через bufconn с боевыми интерцепторами и без сети. Для работы нужен тулчейн Go, а кодогенерация требует protoc или buf — без них шаг сборки не пройдёт.
Какие задачи решает
- спроектировать proto-контракт с версионированием пакета
- свести доменные ошибки к gRPC-кодам в одном месте
- расставить дедлайны и пробросить отмену вниз по вызовам
- вынести логирование и авторизацию в интерцепторы
- протестировать сервис через
bufconnбез сети
Примеры запросов агенту
- Спроектируй proto для сервиса платежей с версией в имени пакета
- Разложи доменные ошибки по gRPC-кодам через
status.Error - Напиши серверный стриминг с завершением по
ctx.Done()
Как установить go-grpc
1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-grpc -a claude-code2Вручную: положить файлы
~/.claude/skills/go-grpc/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/go-grpc/
1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-grpc -a codex2Вручную: положить файлы
.agents/skills/go-grpc/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-grpc -a cursor2Вручную: положить файлы
.cursor/skills/go-grpc/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install eduardo-sl/go-agent-skills go-grpc2Вручную: положить файлы
.github/skills/go-grpc/SKILL.md1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-grpc -a gemini-cli2Командой платформы
gemini skills install https://github.com/eduardo-sl/go-agent-skills --path 'skills/(architecture)/go-grpc'Вручную: положить файлы
.devin/skills/go-grpc/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-grpc -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о go-grpc
Как установить go-grpc в Claude Code?
Выполните npx skills add eduardo-sl/go-agent-skills --skill go-grpc -a claude-code. Скилл появится в ~/.claude/skills/go-grpc/SKILL.md.
Что делает go-grpc?
Задаёт агенту правила разработки gRPC-сервисов на Go: дизайн proto, коды ошибок, дедлайны, интерцепторы, стриминг и тесты через bufconn.
Что нужно для работы скилла?
Тулчейн Go, а для генерации кода — protoc или buf. Скилл рассчитан на Claude и другие агенты для Go-проектов.
Исходный текст
SKILL.md · MIT
---
name: go-grpc
description: >
gRPC services in Go beyond the basics: proto design, status codes and
error details, interceptors, deadlines, streaming, health checks, and
graceful shutdown. Use when: "gRPC service", "proto design", "gRPC error
handling", "interceptor", "gRPC streaming", "gRPC deadline", "grpc health
check", "gRPC status codes".
Not for: REST handlers (go-api-design), protobuf-agnostic layering
(go-architecture-review), TLS hardening (go-security-audit).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain. Requires protoc or buf for code generation.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*) Bash(protoc:*) Bash(buf:*)
metadata:
author: eduardo-sl
version: "1.4.0"
---
# Go gRPC Services
gRPC's contract-first model only pays off if the contract is treated as
an API: versioned packages, deliberate error codes, deadlines
everywhere, and interceptors for everything cross-cutting.
Detailed reference material, loaded on demand:
- `references/clients.md` — `NewClient`, service-config retries and load
balancing, keepalive on both sides, reading status codes.
- `references/testing.md` — in-memory `bufconn` server and client,
asserting status codes.
Read a reference file only when the summary below is not enough.
## 1. Proto Design Rules
```protobuf
syntax = "proto3";
package payment.v1; // version IN the package
option go_package = "github.com/acme/payment-service/gen/payment/v1;paymentv1";
service PaymentService {
rpc CreatePayment(CreatePaymentRequest) returns (CreatePaymentResponse);
}
message CreatePaymentRequest { // one request/response pair
string order_id = 1; // per RPC, always — even if
int64 amount_cents = 2; // empty today
}
message CreatePaymentResponse {
Payment payment = 1;
}
```
- Version in the package (`payment.v1`); breaking change = `payment.v2`.
- Never reuse or renumber field tags; `reserved 3, 7;` deleted ones.
- Dedicated Request/Response messages per RPC — adding a field later is
free; changing a shared message breaks every RPC using it.
- Generate with buf or a pinned protoc in `make generate`; commit
generated code so builds don't depend on toolchain drift.
- In CI, `buf lint` and `buf breaking --against '.git#branch=main'` stop
renumbered fields and removed RPCs before they reach a client.
Field rules (lengths, ranges, formats) belong in protovalidate
annotations, enforced by an interceptor.
## 2. Errors: Status Codes, Not Strings
Return `status.Error`, mapping domain errors in ONE place:
```go
func (s *Server) CreatePayment(ctx context.Context, req *pb.CreatePaymentRequest) (*pb.CreatePaymentResponse, error) {
p, err := s.svc.Create(ctx, toDomain(req))
if err != nil {
return nil, toStatus(err)
}
return &pb.CreatePaymentResponse{Payment: fromDomain(p)}, nil
}
func toStatus(err error) error {
switch {
case errors.Is(err, domain.ErrNotFound):
return status.Error(codes.NotFound, "payment not found")
case errors.Is(err, domain.ErrDuplicate):
return status.Error(codes.AlreadyExists, "payment already exists")
case errors.Is(err, context.DeadlineExceeded):
return status.Error(codes.DeadlineExceeded, "timed out")
default:
return status.Error(codes.Internal, "internal error") // no details leak
}
}
```
Code semantics that matter: `InvalidArgument` (bad request regardless
of state), `FailedPrecondition` (bad state), `NotFound`, `AlreadyExists`,
`Unauthenticated` vs `PermissionDenied`, `Unavailable` (retryable),
`Internal` (bug). Clients read codes with `status.FromError(err)` —
never parse messages.
## 3. Deadlines Are Mandatory
```go
// Client — every call gets a deadline
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
resp, err := client.CreatePayment(ctx, req)
// Server — check before expensive work
if err := ctx.Err(); err != nil {
return nil, status.FromContextError(err).Err()
}
```
The server inherits the client's deadline through the context. Pass
`ctx` into every downstream call (DB, other RPCs) so cancellation
propagates end to end.
## 4. Interceptors for Cross-Cutting Concerns
Handlers stay business-only; recovery, auth, logging, metrics live in
interceptors:
```go
srv := grpc.NewServer(
grpc.ChainUnaryInterceptor(
recoveryInterceptor, // outermost: panic → codes.Internal
loggingInterceptor,
authInterceptor,
),
)
func loggingInterceptor(ctx context.Context, req any,
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
slog.InfoContext(ctx, "rpc",
slog.String("method", info.FullMethod),
slog.Duration("duration", time.Since(start)),
slog.String("code", status.Code(err).String()),
)
return resp, err
}
```
Order matters: recovery first (outermost), then observability, then
auth. Streaming RPCs need the parallel `StreamInterceptor` versions.
## 5. Streaming
- **Server streaming** for large result sets: `stream.Send` in a loop,
return non-nil error to abort with a status.
- **Client/bidi streaming** only when the protocol truly needs it —
each open stream holds a goroutine and flow-control state.
- Always terminate on `ctx.Done()`:
```go
func (s *Server) WatchPayments(req *pb.WatchRequest, stream pb.PaymentService_WatchPaymentsServer) error {
for {
select {
case <-stream.Context().Done():
return status.FromContextError(stream.Context().Err()).Err()
case ev := <-s.events:
if err := stream.Send(toProto(ev)); err != nil {
return err
}
}
}
}
```
## 6. Clients
- `grpc.NewClient` (it replaces the deprecated `Dial`) once per target at
startup; share the `*grpc.ClientConn` — it multiplexes and reconnects.
- Retries, timeouts and load balancing in the service config: retry
`UNAVAILABLE` on idempotent methods only, and use `round_robin` (the
default `pick_first` sends everything to one backend).
- Client keepalive pings must be allowed by the server's
`EnforcementPolicy` (default `MinTime` 5 minutes), or the server closes
the connection with `too_many_pings`.
- Branch on `status.Code(err)`, never on the message.
Examples: `references/clients.md`.
## 7. Testing
Test through the generated client over `bufconn`: a real server with the
production interceptors, no network. Dial `passthrough:///bufnet` with
`WithContextDialer` — `NewClient`'s default `dns` resolver would try to
resolve the name. Assert with `status.Code(err)`. Example:
`references/testing.md`.
## 8. Production Server Setup
```go
lis, err := net.Listen("tcp", cfg.Addr)
if err != nil {
return fmt.Errorf("listen: %w", err)
}
srv := grpc.NewServer(
grpc.Creds(creds), // TLS; mTLS for service-to-service
grpc.MaxRecvMsgSize(4<<20), // explicit; size it to the largest valid request
grpc.ChainUnaryInterceptor(...),
)
pb.RegisterPaymentServiceServer(srv, server)
healthSrv := health.NewServer() // grpc.health.v1 — load balancers need it
healthpb.RegisterHealthServer(srv, healthSrv)
reflection.Register(srv) // grpcurl/debugging; gate on non-prod if policy requires
go func() {
<-ctx.Done()
stopped := make(chan struct{})
go func() { srv.GracefulStop(); close(stopped) }()
select {
case <-stopped: // in-flight RPCs finished
case <-time.After(10 * time.Second):
srv.Stop() // force after grace period
}
}()
return srv.Serve(lis)
```
For mTLS, the server's `tls.Config` sets `ClientCAs` and
`ClientAuth: tls.RequireAndVerifyClientCert`; handlers read the caller's
verified identity from `peer.FromContext(ctx)` rather than trusting
metadata.
## Verification Checklist
1. Proto packages versioned (`*.v1`); no tag reuse; `reserved` for removals
2. Dedicated Request/Response message per RPC
3. Generated code produced by a pinned tool (buf/protoc) and committed; `buf breaking` runs in CI
4. All handler errors are `status.Error` with semantically correct codes
5. `codes.Internal` responses never leak internal error text
6. Every client call has a deadline; ctx propagated through all layers
7. Recovery, logging, auth implemented as chained interceptors (unary + stream)
8. Streams select on `stream.Context().Done()`
9. Health service registered; graceful stop with forced fallback; TLS (mTLS internally) and an explicit receive limit
10. `grpcurl` smoke test (or generated client test) passes against the running server
11. One shared `ClientConn` per target, built with `grpc.NewClient`; retries limited to `UNAVAILABLE`
© eduardo-sl · MIT · eduardo-sl/go-agent-skills