grpc/grpc-go · error
received an illegal stream id
Error message
received an illegal stream id: %v. headers frame: %+v
What it means
Fires in http2Server.operateHeaders (http2_server.go:398) when an incoming HEADERS frame carries a stream ID that is even (server-initiated IDs are reserved) or not strictly greater than the maximum stream ID already seen on this connection. HTTP/2 requires client-initiated stream IDs to be odd and monotonically increasing, so this is a protocol violation by the client.
Solutions
- Identify the offending client from the logged headers frame and upgrade/replace its HTTP/2 or gRPC stack.
- If a middlebox (proxy/LB/sidecar) is in the path, verify it preserves HTTP/2 stream-ID semantics or bypass it to confirm.
- There is no server-side configuration to 'accept' illegal IDs; the connection must be closed per spec, so the fix is on the sender side.
- Ensure your client uses a supported grpc.ClientConn and reuses connections correctly rather than hand-crafting frames.
Example fix
// Server-side: nothing to change; this is a client protocol violation. // On the client, ensure you use grpc.Dial/grpc.NewClient and a current // gRPC version that emits valid stream IDs. conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(creds)) // avoid hand-rolled HTTP/2 framing; let gRPC manage stream IDs.
Defensive patterns
Strategy: try-catch
Try / catch
// Server-side: the connection is closed automatically; handle RPC failures.
if err := srvStream.SendMsg(resp); err != nil {
log.Printf("client connection dropped (possible protocol violation): %v", err)
} Prevention
- Use supported gRPC clients; avoid hand-rolled HTTP/2 framing.
- Ensure middleboxes preserve stream-ID semantics.
- Keep client gRPC versions current.
When it happens
Trigger: A client sends a HEADERS frame with streamID%2==0 (even) or streamID <= t.maxStreamID (reused/decreasing). Produced by a non-conformant HTTP/2 client stack, a buggy gRPC client, or an intermediary that rewrites frames incorrectly. The error message dumps both the bad ID and the full headers frame for diagnosis.
Common situations: Interop with a broken client or proxy that generates stream IDs incorrectly; a client library bug after a reconnect that resets stream-ID accounting; fuzzing or malformed traffic; a middlebox that multiplexes streams from several clients onto one connection with clashing IDs.
Related errors
- received -bytes data exceeding the limit bytes
- transport: timeout string is too long
- transport: timeout string is too short
- transport: timeout unit is not recognized
- ErrCodeEnhanceYourCalm
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/77b161809abeaf64.
Report an issue: GitHub.
Appendix: source
Thrown at internal/transport/http2_server.go:398
defer t.maxStreamMu.Unlock()
streamID := frame.Header().StreamID
// frame.Truncated is set to true when framer detects that the current header
// list size hits MaxHeaderListSize limit.
if frame.Truncated {
t.controlBuf.put(&cleanupStream{
streamID: streamID,
rst: true,
rstCode: http2.ErrCodeFrameSize,
onWrite: func() {},
})
return nil
}
if streamID%2 != 1 || streamID <= t.maxStreamID {
// illegal gRPC stream id.
return fmt.Errorf("received an illegal stream id: %v. headers frame: %+v", streamID, frame)
}
t.maxStreamID = streamID
s := &ServerStream{
Stream: Stream{
id: streamID,
fc: inFlow{limit: uint32(t.initialWindowSize)},
},
st: t,
headerWireLength: int(frame.Header().Length),
}
s.Stream.buf.init()
var (
// if false, content-type was missing or invalid
isGRPC = false
contentType = ""
mdata = make(metadata.MD, len(frame.Fields))
httpMethod stringView on GitHub (pinned to 0c51461d27)