grpc/grpc-go · error

transport: timeout string is too long

Error message

transport: timeout string is too long: %q

What it means

Fires in decodeTimeout (http_util.go:195) when the grpc-timeout header value is longer than 9 characters. The gRPC spec allows at most 8 decimal digits plus a single unit character, to bound the encoded size; longer values are rejected as malformed even if they would otherwise parse.

Solutions

  1. Shorten the grpc-timeout value to <= 8 digits plus a unit (choose a larger unit if needed, e.g. "99999999S" is fine but "999999999S" is not).
  2. Prefer letting gRPC encode the deadline from context.WithTimeout rather than setting grpc-timeout manually.
  3. Audit proxies/LBs for header rewriting that appends or pads characters.

Example fix

// before: manual header with too many digits
// metadata: {"grpc-timeout": "1000000000S"}   // 10 digits -> too long

// after
ctx, cancel := context.WithTimeout(ctx, 1000*time.Second)
defer cancel()
client.Method(ctx, req)   // gRPC emits a spec-compliant value
Defensive patterns

Strategy: validation

Validate before calling

func validTimeoutHeaderLen(s string) bool {
    return len(s) >= 2 && len(s) <= 9 // digits + unit
}

Prevention

When it happens

Trigger: An inbound grpc-timeout value of length > 9. Examples: a caller formatting the timeout with more than 8 significant digits ("123456789S"), padding with leading zeros ("0000000010S"), or appending extra characters; a proxy that mangles the header; a non-conformant client.

Common situations: A custom client/proxy that builds the header without honoring the 8-digit limit; values derived from nanosecond timestamps expressed in hours (huge digit counts); header corruption by a middlebox.

Understand the failure class

Related errors


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

Appendix: source

Thrown at internal/transport/http_util.go:195

	case millisecond:
		return time.Millisecond, true
	case microsecond:
		return time.Microsecond, true
	case nanosecond:
		return time.Nanosecond, true
	default:
	}
	return
}

func decodeTimeout(s string) (time.Duration, error) {
	size := len(s)
	if size < 2 {
		return 0, fmt.Errorf("transport: timeout string is too short: %q", s)
	}
	if size > 9 {
		// Spec allows for 8 digits plus the unit.
		return 0, fmt.Errorf("transport: timeout string is too long: %q", s)
	}
	unit := timeoutUnit(s[size-1])
	d, ok := timeoutUnitToDuration(unit)
	if !ok {
		return 0, fmt.Errorf("transport: timeout unit is not recognized: %q", s)
	}
	t, err := strconv.ParseUint(s[:size-1], 10, 64)
	if err != nil {
		return 0, err
	}
	const maxHours = math.MaxInt64 / uint64(time.Hour)
	if d == time.Hour && t > maxHours {
		// This timeout would overflow math.MaxInt64; clamp it.
		return time.Duration(math.MaxInt64), nil
	}
	return d * time.Duration(t), nil
}

View on GitHub (pinned to 0c51461d27)