Claude Fable 5.1 & GPT-6 Astra packages are live

Rpc

Free · MIT

gRPC and typed RPC — protobuf evolution rules, deadlines and cancellation, streaming, error models, and when RPC beats REST.

242 lines9.2 KB Claude API
Target models
Claude Fable 5.1Claude Opus 5Claude Sonnet 5Claude 5 FamilyFuture Claude Models
Name
rpc
Category
API
Description
gRPC and typed RPC — protobuf evolution rules, deadlines and cancellation, streaming, error models, and when RPC beats REST.
License
MIT
Author
Agent.md maintainers
Last verified
2026-08-23
Reviewed by
unreviewed

FORBIDDEN: Truncating code or writing placeholders such as "// ... existing code ..." or "# rest unchanged". Every edit is complete and applies as written. FORBIDDEN: Reporting a check as passed without showing the command and its output. REQUIRED: Reason through the rules below before the first edit; when two rules conflict, the one stated first wins.

  • Never return INTERNAL for a client mistake — it is unretryable, alerts your on-call, and tells the caller nothing actionable.

#Purpose

Rules for RPC-style APIs — gRPC, Connect, tRPC, Twirp. RPC models procedures, not resources: the unit is an operation with a typed request and response.

Use RPC for internal service-to-service traffic where you control both ends and want a schema-enforced contract, low overhead and streaming. Use REST for public APIs consumed by clients you do not control. → API/rest


#Schema evolution is the whole contract

Protobuf's wire format is positional: field numbers are the contract, names are not.

proto
message Order {
  string id            = 1;
  int64  total_cents   = 2;
  string currency      = 3;
  reserved 4;                        // was `amount`, removed 2026-08 — never reuse
  reserved "amount";
  OrderStatus status   = 5;
}
ChangeSafe
Adding a new field with a new numberYes
Renaming a field (same number)Yes on the wire; breaks JSON mapping and generated code
Removing a fieldOnly with reserved on its number and name
Reusing a field numberNever — old clients decode garbage into the new field
Changing a field's typeNo, except within the documented compatible sets
Changing optional/repeatedNo
Adding an enum valueWire-safe; clients must have a default branch
Renaming a service or methodBreaking — it is the wire path

Reusing a retired field number is the classic protobuf data-corruption bug: a stale client sends its old meaning, and the new server accepts it as the new field. reserved makes it a compile error instead.

Gate it in CI:

bash
buf breaking --against '.git#branch=main'
buf lint

Every enum reserves 0 as *_UNSPECIFIED. Proto3 cannot distinguish an unset scalar from its zero value, so 0 must never be a meaningful state.


#Deadlines and cancellation

Every RPC call sets a deadline. This is not optional and it is the single most common gRPC production failure: a call with no deadline waits forever, holds a connection and a goroutine, and cascades into a fleet-wide hang.

go
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
resp, err := client.GetOrder(ctx, &pb.GetOrderRequest{Id: id})
  • Propagate the deadline through every downstream call. gRPC does this via context automatically — do not create a fresh context.Background() mid-chain, which severs cancellation.
  • Give each hop less budget than its caller, leaving room for the response.
  • Honour cancellation server-side: check ctx.Err() before expensive work and between loop iterations. A client that gave up should not still be costing you.
  • Set a server-side maximum as a backstop against clients that omit deadlines.

Retries must only apply to idempotent methods, with backoff and jitter, and a budget (retryThrottling) so a struggling service is not retried into collapse. → System Design/resilience


#Errors

Use the standard status codes; they carry retry semantics that generated clients and service meshes act on.

CodeMeaningClient retries
INVALID_ARGUMENTRequest is malformed regardless of stateNo
FAILED_PRECONDITIONValid, but system state forbids itNo, until state changes
NOT_FOUNDAbsent, or hidden from this callerNo
PERMISSION_DENIEDAuthenticated, not authorizedNo
UNAUTHENTICATEDMissing or invalid credentialsAfter re-auth
RESOURCE_EXHAUSTEDQuota or rate limitWith backoff
ABORTEDConcurrency conflictYes, at a higher level
UNAVAILABLETransient — the only safely auto-retried codeYes
DEADLINE_EXCEEDEDRan out of timeOnly if idempotent
INTERNALServer bugNo

Attach machine-readable detail with google.rpc.ErrorInfo and BadRequest rather than encoding structure into the message string.

Never return INTERNAL for a client mistake — it is unretryable, alerts your on-call, and tells the caller nothing actionable.


#Streaming

PatternUse for
UnaryDefault. Use it unless you have a reason not to
Server streamingLarge result sets, live feeds
Client streamingBulk upload, telemetry
BidirectionalInteractive sessions

Streams are stateful and complicate load balancing, retries and deployments — a long-lived stream pins a client to one pod across a rollout.

  • Bound stream lifetime and message size (grpc.max_receive_message_length).
  • Apply flow control; an unbounded producer will exhaust the consumer's memory.
  • Design reconnection with resume tokens, because streams will break.
  • Server streaming is not a substitute for pagination when the client wants a page. → API/pagination

#Operational rules

  • Connection-level load balancing fails with HTTP/2. gRPC multiplexes over one long-lived connection, so an L4 balancer pins all traffic to one backend. Use an L7 proxy (Envoy, Linkerd) or client-side load balancing with resolver updates.
  • Implement the standard health checking protocol (grpc.health.v1.Health) and wire it to readiness probes.
  • Enable reflection in development only; it exposes the full service surface.
  • Use TLS everywhere, mTLS between internal services.
  • Instrument with interceptors: request id propagation, structured logging, metrics by method and status code, tracing. → Backend/monitoring
  • For browser clients, gRPC needs a proxy (grpc-web) — Connect speaks both protocols and is usually the better choice there.

#Anti-patterns

Anti-patternWhy it failsFix
Reusing a retired field numberOld clients corrupt new datareserved number and name
Removing a field without reservedNumber becomes reusable by accidentAlways reserve
Meaningful enum 0Indistinguishable from unset in proto3*_UNSPECIFIED = 0
No breaking-change gateIncompatible schemas shipbuf breaking in CI
Calls without deadlinesHangs cascade fleet-wideDeadline on every call
Fresh context.Background() mid-chainCancellation and deadline lostPropagate the context
Ignoring ctx.Err() server-sideWork continues for a gone clientCheck between steps
Retrying non-idempotent methodsDuplicate side effectsRetry only idempotent calls
Retries without a budgetRetry storm collapses the serviceBackoff, jitter, throttling
INTERNAL for bad inputUnretryable, pages on-call, unactionableINVALID_ARGUMENT
Error detail encoded in the messageClients parse proseErrorInfo details
L4 load balancer in front of gRPCAll traffic pins to one backendL7 proxy or client-side LB
Streaming where unary would doStateful, breaks rollouts and balancingUnary by default
Unbounded stream or message sizeMemory exhaustionExplicit limits and flow control
Reflection enabled in productionFull surface disclosedDevelopment only

#Checklist

  • Field numbers are never reused; removals use reserved for number and name
  • Every enum reserves 0 as UNSPECIFIED
  • buf lint and buf breaking run in CI against the merge base
  • Every client call sets an explicit deadline
  • Deadlines propagate through the call chain with a shrinking budget
  • Servers check for cancellation before and during expensive work
  • A server-side maximum deadline exists as a backstop
  • Retries apply only to idempotent methods, with backoff, jitter and a budget
  • Status codes are used correctly, with structured error details
  • Unary is the default; streaming is justified per method
  • Message size and stream lifetime are bounded; flow control is applied
  • Streams have a documented reconnection and resume strategy
  • Load balancing is L7 or client-side, not L4
  • The standard health-checking service is implemented and wired to probes
  • Reflection is disabled in production; TLS/mTLS is enforced
  • Interceptors provide request ids, structured logs, metrics and traces