grpc/grpc-go · critical

received an illegal stream id: %v. headers frame: %+v

Error message

received an illegal stream id: %v. headers frame: %+v

What it means

This error occurs in operateHeaders when an incoming HEADERS frame has a stream ID that is either even (client-initiated streams must use odd IDs per HTTP/2 spec) or less than or equal to the maximum stream ID already seen (streams must be monotonically increasing). This is treated as a protocol violation and triggers a GOAWAY with PROTOCOL_ERROR.

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  string

View on GitHub (pinned to 03255a9237)

Solutions

  1. Identify the client library/implementation generating invalid stream IDs — this is a client-side protocol bug.
  2. Upgrade the client's HTTP/2 or gRPC library to a compliant version.
  3. If behind a proxy/L7 load balancer, verify it correctly forwards HTTP/2 stream IDs.
  4. Check for concurrent connections being multiplexed incorrectly by an intermediary.

Example fix

// No client-side code fix — this is a protocol-level violation by the peer.
// Ensure the client uses a compliant gRPC/HTTP2 library:

// before (broken): custom/raw HTTP2 client with bad stream ID management
// (even or reused stream IDs)

// after (valid): use grpc-go or another compliant gRPC client library
conn, err := grpc.Dial(target, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
Defensive patterns

Strategy: validation

Try / catch

// This error causes a GOAWAY — the connection is terminated.
// On the client side, handle connection-level errors with reconnection:
if status.Code(err) == codes.Unavailable {
    if strings.Contains(err.Error(), "GOAWAY") || strings.Contains(err.Error(), "protocol") {
        // gRPC client will automatically reconnect via the name resolver
        log.Printf("connection terminated due to protocol violation")
    }
}

Prevention

When it happens

Trigger: A client sends a HEADERS frame with an even stream ID, or reuses/collides with a stream ID lower than a previously established stream. The server detects the violation in operateHeaders and returns the error which causes a connection-level GOAWAY.

Common situations: Buggy or non-compliant HTTP/2 client library that generates incorrect stream IDs, a client implementing HTTP/2 multiplexing incorrectly, connection hijacking/probing by a non-gRPC client, or a badly behaving intermediary/proxy that reuses stream IDs.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/77b161809abeaf64. Report an issue: GitHub.