grpc/grpc-go · error

malformed grpc-timeout: %v

Error message

malformed grpc-timeout: %v

What it means

NewServerHandlerTransport (handler_server.go:53) parses the grpc-timeout header via decodeTimeout (http_util.go:188). If the header is present but malformed, it writes HTTP 400 and returns this error (codes.Internal wrapping the decode error). decodeTimeout fails when the value is too short (<2 chars), too long (>9 chars), has an unrecognized unit char, or the numeric part isn't a valid uint.

Source

Thrown at internal/transport/handler_server.go:109

	}
	st := &serverHandlerTransport{
		rw:             w,
		req:            r,
		closedCh:       make(chan struct{}),
		writes:         make(chan func()),
		peer:           p,
		contentType:    contentType,
		contentSubtype: contentSubtype,
		stats:          stats,
		bufferPool:     bufferPool,
	}
	st.logger = prefixLoggerForServerHandlerTransport(st)

	if v := r.Header.Get("grpc-timeout"); v != "" {
		to, err := decodeTimeout(v)
		if err != nil {
			msg := fmt.Sprintf("malformed grpc-timeout: %v", err)
			http.Error(w, msg, http.StatusBadRequest)
			return nil, status.Error(codes.Internal, msg)
		}
		st.timeoutSet = true
		st.timeout = to
	}

	metakv := []string{"content-type", contentType}
	if r.Host != "" {
		metakv = append(metakv, ":authority", r.Host)
	}
	for k, vv := range r.Header {
		k = strings.ToLower(k)
		if isReservedHeader(k) && !isWhitelistedHeader(k) {
			continue
		}
		for _, v := range vv {
			v, err := decodeMetadataHeader(k, v)
			if err != nil {

View on GitHub (pinned to 03255a9237)

Solutions

  1. Ensure the grpc-timeout header is produced by a compliant gRPC client library (set via context.WithTimeout/grpc timeout, not hand-crafted).
  2. If a proxy sets grpc-timeout, validate it matches <1-8 digits><H|M|S|m|u|n> before forwarding.
  3. Strip any malformed grpc-timeout at the proxy edge so the gRPC server treats the request as having no deadline.

Example fix

// before (malformed header injected by a proxy)
//   grpc-timeout: 5000
// server logs: malformed grpc-timeout

// after: send a well-formed timeout, or omit it
//   grpc-timeout: 5000m  (5000 milliseconds)
// Prefer setting it client-side:
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
conn.Invoke(ctx, "/svc/Method", req, resp)
Defensive patterns

Strategy: validation

Validate before calling

// Validate a grpc-timeout value before forwarding (edge/proxy)
import "regexp"
var grpcTimeoutRe = regexp.MustCompile(`^[0-9]{1,8}[HMSmun]$`)
func validGrpcTimeout(v string) bool { return grpcTimeoutRe.MatchString(v) }
// if !validGrpcTimeout(h.Get("grpc-timeout")) { h.Del("grpc-timeout") }

Try / catch

// NewServerHandlerTransport returns this as an error; handle it
st, err := transport.NewServerHandlerTransport(w, r, stats, pool)
if err != nil {
    // already wrote HTTP 400/500 to w; just abort the handler
    return
}

Prevention

When it happens

Trigger: A client or proxy sends a grpc-timeout header whose value does not match the gRPC timeout format: <digits><unit> where unit is one of H M S m u n. Examples: "100" (no unit), "1X" (bad unit), "1234567890S" (too long), "abc" (non-numeric).

Common situations: A non-gRPC client or a misbehaving proxy injecting a malformed grpc-timeout; a custom client building the header by hand instead of using the gRPC library; an intermediary that rewrites/truncates the header.

Understand the failure class

Related errors


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