grpc/grpc-go · error

external processor returned unexpected status

Error message

external processor returned unexpected status %v for request headers, expected %v

What it means

In processInitialHeaders (ext_proc.go:1720), the CommonResponse.Status of a request headers response must be CommonResponse_CONTINUE. Any other value is rejected because the client filter only supports continuing the request through the dataplane at this phase; aborting must use an immediate_response instead.

Solutions

  1. Set CommonResponse.Status = CommonResponse_CONTINUE on every request_headers response.
  2. To short-circuit the request, send an immediate_response instead of mutating the headers response status.
  3. Audit the server response builder to ensure status is explicitly initialized.

Example fix

// before
&procpb.HeadersResponse{Response: &procpb.CommonResponse{ /* status unset */ }}

// after
&procpb.HeadersResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE}}
Defensive patterns

Strategy: validation

Validate before calling

// Server-side: always set CONTINUE on header responses
func headerResponse() *procpb.HeadersResponse {
    return &procpb.HeadersResponse{Response: &procpb.CommonResponse{Status: procpb.CommonResponse_CONTINUE}}
}

Try / catch

// Client-side: surfaces as codes.Internal.
if st, ok := status.FromError(err); ok && strings.Contains(st.Message(), "unexpected status") {
    // server set a non-CONTINUE status; fix the server
}

Prevention

When it happens

Trigger: External processor returns a request_headers response whose response.status is set to a value other than CONTINUE (e.g., a numeric default of 0 in some implementations that maps to a non-CONTINUE enum value, or an explicit custom status).

Common situations: Server-side bug defaulting the status field, or a server attempting to short-circuit a request by setting a non-CONTINUE status on the headers response rather than using an immediate_response.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/1a7d18e01cc5fbe8. Report an issue: GitHub.

Appendix: source

Thrown at internal/xds/httpfilter/extproc/ext_proc.go:1721

		return false
	}
	if resp.GetRequestDrain() {
		cs.triggerBypass()
	}
	if resp.GetImmediateResponse() != nil {
		cs.handleImmediateResponse(resp.GetImmediateResponse(), newStream, opts)
		return false
	}

	header := resp.GetRequestHeaders()
	if header == nil {
		err := fmt.Errorf("external processor returned an unexpected message type %T, expected request headers response", resp.GetResponse())
		cs.handleHeaderError(err, newStream, opts)
		return false
	}
	// Check if status in header response is CONTINUE; if not, fail the stream.
	if status := header.GetResponse().GetStatus(); status != v3procservicepb.CommonResponse_CONTINUE {
		cs.handleHeaderError(fmt.Errorf("external processor returned unexpected status %v for request headers, expected %v", status, v3procservicepb.CommonResponse_CONTINUE), newStream, opts)
		return false
	}
	// Mutate the outgoing headers with additions and removals received from the
	// external processor.
	outgoingMD, _ := metadata.FromOutgoingContext(ctx)
	if outgoingMD == nil {
		outgoingMD = metadata.MD{}
	}
	if err = cs.applyMutations(header.GetResponse().GetHeaderMutation(), outgoingMD); err != nil {
		cs.handleHeaderError(err, newStream, opts)
		return false
	}
	dataplaneCtx := metadata.NewOutgoingContext(ctx, outgoingMD)
	if err = cs.createDataplaneStream(dataplaneCtx, newStream, opts); err != nil {
		return false
	}
	return true
}

View on GitHub (pinned to 0c51461d27)