{"id":"8063aa60-b6c8-4572-bf3c-c20ab015e512","shortId":"hYHmNC","kind":"skill","title":"golang-grpc","tagline":"Provides gRPC usage guidelines, protobuf organization, and production-ready patterns for Golang microservices. Use when implementing, reviewing, or debugging gRPC servers/clients, writing proto files, setting up interceptors, handling gRPC errors with status codes, configuring TL","description":"**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.\n\n**Modes:**\n\n- **Build mode** — implementing a new gRPC server or client from scratch.\n- **Review mode** — auditing existing gRPC code for correctness, security, and operability issues.\n\n# Go gRPC Best Practices\n\nTreat gRPC as a pure transport layer — keep it separate from business logic. The official Go implementation is `google.golang.org/grpc`.\n\nThis skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform.\n\n## Quick Reference\n\n| Concern | Package / Tool |\n| --- | --- |\n| Service definition | `protoc` or `buf` with `.proto` files |\n| Code generation | `protoc-gen-go`, `protoc-gen-go-grpc` |\n| Error handling | `google.golang.org/grpc/status` with `codes` |\n| Rich error details | `google.golang.org/genproto/googleapis/rpc/errdetails` |\n| Interceptors | `grpc.ChainUnaryInterceptor`, `grpc.ChainStreamInterceptor` |\n| Middleware ecosystem | `github.com/grpc-ecosystem/go-grpc-middleware` |\n| Testing | `google.golang.org/grpc/test/bufconn` |\n| TLS / mTLS | `google.golang.org/grpc/credentials` |\n| Health checks | `google.golang.org/grpc/health` |\n\n## Proto File Organization\n\nOrganize 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`.\n\n[Proto & code generation reference](references/protoc-reference.md)\n\n## Server Implementation\n\n- Implement health check service (`grpc_health_v1`) — Kubernetes probes need it to determine readiness\n- Use interceptors for cross-cutting concerns (logging, auth, recovery) — keeps business logic clean\n- Use `GracefulStop()` with a timeout fallback to `Stop()` — drains in-flight RPCs while preventing hangs\n- Disable reflection in production — it exposes your full API surface\n\n```go\nsrv := grpc.NewServer(\n    grpc.ChainUnaryInterceptor(loggingInterceptor, recoveryInterceptor),\n)\npb.RegisterUserServiceServer(srv, svc)\nhealthpb.RegisterHealthServer(srv, health.NewServer())\n\ngo srv.Serve(lis)\n\n// On shutdown signal:\nstopped := make(chan struct{})\ngo func() { srv.GracefulStop(); close(stopped) }()\nselect {\ncase <-stopped:\ncase <-time.After(15 * time.Second):\n    srv.Stop()\n}\n```\n\n### Interceptor Pattern\n\n```go\nfunc loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {\n    start := time.Now()\n    resp, err := handler(ctx, req)\n    log.Printf(\"method=%s duration=%s code=%s\", info.FullMethod, time.Since(start), status.Code(err))\n    return resp, err\n}\n```\n\n## Client Implementation\n\n- Reuse connections — gRPC multiplexes RPCs on a single HTTP/2 connection; one-per-request wastes TCP/TLS handshakes\n- Set deadlines on every call (`context.WithTimeout`) — without one, a slow upstream hangs goroutines indefinitely\n- Use `round_robin` with headless Kubernetes services via `dns:///` scheme\n- Pass metadata (auth tokens, trace IDs) via `metadata.NewOutgoingContext`\n\n```go\nconn, err := grpc.NewClient(\"dns:///user-service:50051\",\n    grpc.WithTransportCredentials(creds),\n    grpc.WithDefaultServiceConfig(`{\n        \"loadBalancingPolicy\": \"round_robin\",\n        \"methodConfig\": [{\n            \"name\": [{\"service\": \"\"}],\n            \"timeout\": \"5s\",\n            \"retryPolicy\": {\n                \"maxAttempts\": 3,\n                \"initialBackoff\": \"0.1s\",\n                \"maxBackoff\": \"1s\",\n                \"backoffMultiplier\": 2,\n                \"retryableStatusCodes\": [\"UNAVAILABLE\"]\n            }\n        }]\n    }`),\n)\nclient := pb.NewUserServiceClient(conn)\n```\n\n## Error Handling\n\nAlways 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.\n\n| Code                 | When to Use                                 |\n| -------------------- | ------------------------------------------- |\n| `InvalidArgument`    | Malformed input (missing field, bad format) |\n| `NotFound`           | Entity does not exist                       |\n| `AlreadyExists`      | Create failed, entity exists                |\n| `PermissionDenied`   | Caller lacks permission                     |\n| `Unauthenticated`    | Missing or invalid token                    |\n| `FailedPrecondition` | System not in required state                |\n| `ResourceExhausted`  | Rate limit or quota exceeded                |\n| `Unavailable`        | Transient issue, safe to retry              |\n| `Internal`           | Unexpected bug                              |\n| `DeadlineExceeded`   | Timeout                                     |\n\n```go\n// ✗ Bad — caller gets codes.Unknown, can't decide whether to retry\nreturn nil, fmt.Errorf(\"user not found\")\n\n// ✓ Good — specific code lets clients act appropriately\nif errors.Is(err, ErrNotFound) {\n    return nil, status.Errorf(codes.NotFound, \"user %q not found\", req.UserId)\n}\nreturn nil, status.Errorf(codes.Internal, \"lookup failed: %v\", err)\n```\n\nFor field-level validation errors, attach `errdetails.BadRequest` via `status.WithDetails`.\n\n## Streaming\n\n| Pattern | Use Case |\n| --- | --- |\n| Server streaming | Server sends a sequence (log tailing, result sets) |\n| Client streaming | Client sends a sequence, server responds once (file upload, batch) |\n| Bidirectional | Both send independently (chat, real-time sync) |\n\nPrefer streaming over large single messages — avoids per-message size limits and lowers memory pressure.\n\n```go\nfunc (s *server) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {\n    for _, u := range users {\n        if err := stream.Send(u); err != nil {\n            return err\n        }\n    }\n    return nil\n}\n```\n\n## Testing\n\nUse `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.\n\n[Testing patterns and examples](references/testing.md)\n\n## Security\n\n- TLS MUST be enabled in production — credentials travel in metadata\n- For service-to-service auth, use mTLS or delegate to a service mesh (Istio, Linkerd)\n- For user auth, implement `credentials.PerRPCCredentials` and validate tokens in an auth interceptor\n- Reflection SHOULD be disabled in production to prevent API discovery\n\n## Performance\n\n| Setting | Purpose | Typical Value |\n| --- | --- | --- |\n| `keepalive.ServerParameters.Time` | Ping interval for idle connections | 30s |\n| `keepalive.ServerParameters.Timeout` | Ping ack timeout | 10s |\n| `grpc.MaxRecvMsgSize` | Override 4 MB default for large payloads | 16 MB |\n| Connection pooling | Multiple conns for high-load streaming | 4 connections |\n\nMost services do not need connection pooling — profile before adding complexity.\n\n## Common Mistakes\n\n| Mistake | Fix |\n| --- | --- |\n| Returning raw `error` | Becomes `codes.Unknown` — client can't decide whether to retry. Use `status.Errorf` with a specific code |\n| No deadline on client calls | Slow upstream hangs indefinitely. Always `context.WithTimeout` |\n| New connection per request | Wastes TCP/TLS handshakes. Create once, reuse — HTTP/2 multiplexes RPCs |\n| Reflection enabled in production | Lets attackers enumerate every method. Enable only in dev/staging |\n| `codes.Internal` for all errors | Wrong codes break client retry logic. `Unavailable` triggers retry; `InvalidArgument` does not |\n| Bare types as RPC arguments | Can't add fields to `string`. Wrapper messages allow backwards-compatible evolution |\n| Missing health check service | Kubernetes can't determine readiness, kills pods during deployments |\n| Ignoring context cancellation | Long operations continue after caller gave up. Check `ctx.Err()` |\n\n## Cross-References\n\n- → See `samber/cc-skills-golang@golang-context` skill for deadline and cancellation patterns\n- → See `samber/cc-skills-golang@golang-error-handling` skill for gRPC error to Go error mapping\n- → See `samber/cc-skills-golang@golang-observability` skill for gRPC interceptors (logging, tracing, metrics)\n- → See `samber/cc-skills-golang@golang-testing` skill for gRPC testing with bufconn","tags":["golang","grpc","skills","samber","agent","agent-skills","antigravity","claude","claude-code","code","codex","coding"],"capabilities":["skill","source-samber","skill-golang-grpc","topic-agent","topic-agent-skills","topic-antigravity","topic-claude","topic-claude-code","topic-code","topic-codex","topic-coding","topic-copilot","topic-cursor","topic-gemini","topic-gemini-cli-extension"],"categories":["cc-skills-golang"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/samber/cc-skills-golang/golang-grpc","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add samber/cc-skills-golang","source_repo":"https://github.com/samber/cc-skills-golang","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 1725 github stars · SKILL.md body (8,458 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:53:01.322Z","embedding":null,"createdAt":"2026-04-18T20:32:20.184Z","updatedAt":"2026-05-18T18:53:01.322Z","lastSeenAt":"2026-05-18T18:53:01.322Z","tsv":"'/genproto/googleapis/rpc/errdetails':179 '/grpc':119 '/grpc-ecosystem/go-grpc-middleware':187 '/grpc/credentials':196 '/grpc/health':201 '/grpc/status':171 '/grpc/test/bufconn':191 '/user-service':420 '0.1':437 '10s':767 '15':326 '16':776 '1s':440 '2':442 '3':435 '30s':762 '4':770,787 '50051':421 '5s':432 'ack':765 'act':557 'action':469 'ad':225,798 'add':882 'allow':888 'alreadyexist':498 'alway':212,450,686,831 'api':292,749 'appropri':558 'argument':879 'attach':586 'attack':851 'audit':85 'auth':262,410,718,731,739 'avoid':631 'backoffmultipli':441 'backward':890 'backwards-compat':889 'bad':491,536 'bare':218,875 'batch':615 'becom':463,807 'best':97 'bidirect':616 'break':865 'buf':152,229 'bufconn':668,968 'bug':532 'build':72 'busi':110,265 'call':389,826 'caller':504,537,913 'cancel':908,930 'cannot':222 'case':322,324,593 'chan':314 'chat':620 'check':198,242,895,916 'clean':267 'client':80,366,445,467,470,556,604,606,809,825,866 'close':319 'code':37,58,88,131,156,173,234,356,459,472,482,554,696,821,864 'codes.internal':575,859 'codes.notfound':566 'codes.unknown':464,539,808 'common':800 'compat':891 'complex':799 'concern':145,260 'configur':38 'conn':417,447,781 'connect':369,377,673,761,778,788,794,834 'context':907,925 'context.context':335 'context.withtimeout':390,832 'context7':136 'continu':911 'correct':53,90 'creat':499,840 'cred':423 'credenti':709 'credentials.perrpccredentials':733 'cross':258,919 'cross-cut':257 'cross-refer':918 'ctx':334,349 'ctx.err':917 'cut':259 'deadlin':59,386,823,928 'deadlineexceed':533 'debug':23 'decid':474,542,812 'default':772 'definit':149 'degrad':481 'deleg':722 'deploy':905 'design':49 'detail':176 'determin':252,900 'dev/staging':858 'directori':210 'disabl':284,744 'discover':141 'discoveri':750 'distribut':45 'document':129 'domain':207 'drain':276 'durat':354 'ecosystem':184 'enabl':706,847,855 'engin':47 'entiti':494,501 'enumer':852 'err':347,362,365,418,561,579,657,660,663 'errdetails.badrequest':587 'errnotfound':562 'error':34,167,175,343,448,453,462,585,651,689,806,862,936,941,944 'errors.is':560 'everi':388,853 'evolut':892 'exampl':132,700 'exceed':523 'exercis':675 'exhaust':124 'exist':86,497,502 'expect':693 'expos':289 'fail':478,500,577 'fail-fast':477 'failedprecondit':512 'fallback':273 'fast':479 'field':224,490,582,883 'field-level':581 'file':28,155,203,613 'fix':803 'flight':279 'fmt.errorf':548 'format':492 'found':551,570 'full':291,677 'func':317,332,642 'gave':914 'gen':160,164 'generat':157,227,230,235 'get':538 'github.com':186 'github.com/grpc-ecosystem/go-grpc-middleware':185 'go':44,95,114,161,165,294,306,316,331,416,535,641,943 'golang':2,16,924,935,949,961 'golang-context':923 'golang-error-handl':934 'golang-grpc':1 'golang-observ':948 'golang-test':960 'good':552 'google.golang.org':118,170,178,190,195,200 'google.golang.org/genproto/googleapis/rpc/errdetails':177 'google.golang.org/grpc':117 'google.golang.org/grpc/credentials':194 'google.golang.org/grpc/health':199 'google.golang.org/grpc/status':169 'google.golang.org/grpc/test/bufconn':189 'goroutin':397 'grace':62 'gracefulstop':269 'grpc':3,5,24,33,50,77,87,96,100,166,244,370,452,678,694,940,953,965 'grpc.chainstreaminterceptor':182 'grpc.chainunaryinterceptor':181,297 'grpc.maxrecvmsgsize':768 'grpc.newclient':419 'grpc.newserver':296 'grpc.unaryhandler':341 'grpc.unaryserverinfo':339 'grpc.withdefaultserviceconfig':424 'grpc.withtransportcredentials':422 'guidelin':7 'handl':32,168,449,937 'handler':340,348 'handshak':384,839 'hang':283,396,829 'happi':69 'headless':403 'health':197,241,245,894 'health.newserver':305 'healthpb.registerhealthserver':303 'help':138 'high':784 'high-load':783 'http/2':376,843 'id':413 'idl':760 'ignor':906 'implement':20,74,115,239,240,367,732 'in-flight':277 'in-memori':670 'indefinit':398,830 'independ':619 'info':338 'info.fullmethod':358 'inform':135 'initialbackoff':436 'input':488 'interceptor':31,60,180,255,329,681,740,954 'intern':530 'interv':758 'invalid':510 'invalidargu':486,872 'issu':94,526 'istio':727 'keep':106,264 'keepalive.serverparameters.time':756 'keepalive.serverparameters.timeout':763 'kill':902 'kubernet':247,404,897 'lack':505 'larg':628,774 'later':226 'layer':105 'let':555,850 'level':583 'librari':128 'like':220 'limit':520,636 'linkerd':728 'lis':308 'listus':645 'listusersserv':650 'load':785 'loadbalancingpolici':425 'log':261,600,955 'log.printf':351 'logginginterceptor':298,333 'logic':111,266,868 'long':909 'lookup':576 'lower':638 'make':313 'malform':487 'map':945 'matter':64 'maxattempt':434 'maxbackoff':439 'mb':771,777 'memori':639,672 'mesh':726 'messag':217,630,634,887 'metadata':409,682,712 'metadata.newoutgoingcontext':415 'method':352,854 'methodconfig':428 'metric':957 'microservic':17 'middlewar':183 'miss':489,508,893 'mistak':801,802 'mode':71,73,84 'mtls':193,720 'much':66 'multipl':780 'multiplex':371,844 'must':704 'name':429 'need':249,793 'network':684 'new':76,833 'nil':547,564,573,661,665 'notfound':493 'noth':468 'observ':950 'offici':113 'one':379,392 'one-per-request':378 'oper':55,93,910 'organ':9,204,205 'overhead':685 'overrid':769 'packag':146 'pass':408 'path':70 'pattern':14,330,591,698,931 'payload':775 'pb.listusersrequest':647 'pb.newuserserviceclient':446 'pb.registeruserserviceserver':300 'pb.userservice':649 'per':380,633,835 'per-messag':632 'perform':751 'permiss':506 'permissiondeni':503 'persona':40 'ping':757,764 'platform':142 'pleas':125 'pod':903 'pool':779,795 'practic':98 'prefer':625 'pressur':640 'prevent':282,748 'probe':248 'product':12,287,708,746,849 'production-readi':11 'profil':796 'proper':56 'proto':27,154,202,233 'proto/user/v1':211 'protobuf':8 'protoc':150,159,163,232 'protoc-gen-go':158 'protoc-gen-go-grpc':162 'provid':4 'pure':103 'purpos':753 'q':568 'quick':143 'quota':522 'rang':654 'rate':519 'raw':461,805 'readi':13,253,901 'real':622 'real-tim':621 'recoveri':263 'recoveryinterceptor':299 'refer':126,144,236,920 'references/protoc-reference.md':237 'references/testing.md':701 'reflect':285,741,846 'req':336,350,646 'req.userid':571 'request':214,381,836 'requir':516 'resourceexhaust':518 'resp':346,364 'respond':611 'respons':215 'result':602 'retri':475,529,545,815,867,871 'retryablestatuscod':443 'retrypolici':433 'return':363,451,546,563,572,662,664,691,804 'reus':368,842 'review':21,83 'rich':174 'robin':401,427 'round':400,426 'rpc':878 'rpcs':280,372,845 'safe':527 'samber/cc-skills-golang':922,933,947,959 'scenario':690 'scheme':407 'scratch':82 'secur':91,702 'see':921,932,946,958 'select':321 'send':597,607,618 'separ':108 'sequenc':599,609 'serial':680 'server':78,238,594,596,610,644 'servers/clients':25 'servic':51,148,243,405,430,715,717,725,790,896 'service-to-servic':714 'set':29,385,603,752 'shutdown':63,310 'signal':311 'singl':375,629 'size':635 'skill':121,926,938,951,963 'skill-golang-grpc' 'slow':394,827 'source-samber' 'specif':458,553,820 'srv':295,301,304 'srv.gracefulstop':318 'srv.serve':307 'srv.stop':328 'stack':679 'start':344,360 'state':517 'status':36,57,695 'status.code':361 'status.error':455 'status.errorf':565,574,817 'status.withdetails':589 'stop':275,312,320,323 'stream':590,595,605,626,648,786 'stream.send':658 'string':221,885 'struct':315 'surfac':293 'svc':302 'sync':624 'system':46,513 'tail':601 'tcp/tls':383,838 'tell':465 'test':188,666,687,697,962,966 'time':623 'time.after':325 'time.now':345 'time.second':327 'time.since':359 'timeout':272,431,534,766 'tl':39 'tls':192,703 'token':411,511,736 'tool':147 'topic-agent' 'topic-agent-skills' 'topic-antigravity' 'topic-claude' 'topic-claude-code' 'topic-code' 'topic-codex' 'topic-coding' 'topic-copilot' 'topic-cursor' 'topic-gemini' 'topic-gemini-cli-extension' 'trace':412,956 'transient':525 'transport':104 'travel':710 'treat':99 'trigger':870 'type':219,876 'typic':754 'u':653,659 'unauthent':507 'unavail':444,524,869 'unexpect':531 'upload':614 'upstream':395,828 'usag':6 'use':18,213,254,268,399,454,471,485,592,667,719,816 'user':549,567,655,730 'v':578 'v1':246 'valid':584,735 'valu':755 'version':209 'via':406,414,588 'vs':476,480 'wast':382,837 'whether':543,813 'without':391,683 'wrapper':216,886 'write':26 'wrong':863","prices":[{"id":"583ff333-1581-4bea-baf2-d4a718cca336","listingId":"8063aa60-b6c8-4572-bf3c-c20ab015e512","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"samber","category":"cc-skills-golang","install_from":"skills.sh"},"createdAt":"2026-04-18T20:32:20.184Z"}],"sources":[{"listingId":"8063aa60-b6c8-4572-bf3c-c20ab015e512","source":"github","sourceId":"samber/cc-skills-golang/golang-grpc","sourceUrl":"https://github.com/samber/cc-skills-golang/tree/main/skills/golang-grpc","isPrimary":false,"firstSeenAt":"2026-04-18T21:55:11.704Z","lastSeenAt":"2026-05-18T18:53:01.322Z"},{"listingId":"8063aa60-b6c8-4572-bf3c-c20ab015e512","source":"skills_sh","sourceId":"samber/cc-skills-golang/golang-grpc","sourceUrl":"https://skills.sh/samber/cc-skills-golang/golang-grpc","isPrimary":true,"firstSeenAt":"2026-04-18T20:32:20.184Z","lastSeenAt":"2026-05-07T22:40:27.574Z"}],"details":{"listingId":"8063aa60-b6c8-4572-bf3c-c20ab015e512","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"samber","slug":"golang-grpc","github":{"repo":"samber/cc-skills-golang","stars":1725,"topics":["agent","agent-skills","ai","antigravity","claude","claude-code","code","codex","coding","copilot","cursor","gemini","gemini-cli-extension","openclaw","opencode","plugin","skills","skillsmp","vibe-coding"],"license":"mit","html_url":"https://github.com/samber/cc-skills-golang","pushed_at":"2026-05-18T17:36:00Z","description":"🧑‍🎨 A collection of Golang agentic skills that works","skill_md_sha":"91963fe35da3e508fd0624c5631f40e06d21c375","skill_md_path":"skills/golang-grpc/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/samber/cc-skills-golang/tree/main/skills/golang-grpc"},"layout":"multi","source":"github","category":"cc-skills-golang","frontmatter":{"name":"golang-grpc","license":"MIT","description":"Provides gRPC usage guidelines, protobuf organization, and production-ready patterns for Golang microservices. Use when implementing, reviewing, or debugging gRPC servers/clients, writing proto files, setting up interceptors, handling gRPC errors with status codes, configuring TLS/mTLS, testing with bufconn, or working with streaming RPCs.","compatibility":"Designed for Claude Code or similar AI coding agents, and for projects using Golang."},"skills_sh_url":"https://skills.sh/samber/cc-skills-golang/golang-grpc"},"updatedAt":"2026-05-18T18:53:01.322Z"}}