grpc/grpc-go · error

transport: timeout unit is not recognized

Error message

transport: timeout unit is not recognized: %q

What it means

Fires in decodeTimeout (http_util.go:200) when the last character of the grpc-timeout header is not one of the recognized unit suffixes. Valid units are H (hour), M (minute), S (second), m (millisecond), u (microsecond), n (nanosecond); anything else (including uppercase variants of the sub-second units) is rejected.

Solutions

  1. Use the correct single-character unit: H, M, S, m, u, n. Note seconds are 'S' (uppercase) and milliseconds are 'm' (lowercase).
  2. Do not set grpc-timeout by hand; let gRPC derive it from the context deadline.
  3. If you must build it, follow the encoding helper used by the library (encodeTimeout) rather than time.Duration.String().

Example fix

// before: wrong unit suffixes
// metadata: {"grpc-timeout": "5s"}     // lowercase 's' invalid
// metadata: {"grpc-timeout": "1000ms"}  // 'ms' invalid

// after
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
client.Method(ctx, req)   // emits "5S" automatically
Defensive patterns

Strategy: validation

Validate before calling

var validTimeoutUnits = map[byte]bool{'H': true, 'M': true, 'S': true, 'm': true, 'u': true, 'n': true}

func validTimeoutUnit(s string) bool {
    return len(s) >= 2 && validTimeoutUnits[s[len(s)-1]]
}

Prevention

When it happens

Trigger: An inbound grpc-timeout value whose final character is not in {H, M, S, m, u, n}. Examples: "1000ms" (wrong: spec uses 'm' for millisecond, not 'ms'), "5MS", "10x", "3s" (lowercase 's' is not valid; seconds use uppercase 'S'). The full offending string is reported.

Common situations: A caller formatting the timeout in Go's time.ParseDuration style ("5s", "100ms") instead of the gRPC HTTP/2 wire format; a non-gRPC client; a proxy that re-encodes the header; documentation misread where 'ms' looks like the millisecond unit.

Understand the failure class

Related errors


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

Appendix: source

Thrown at internal/transport/http_util.go:200

		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
}

const (
	spaceByte   = ' '
	tildeByte   = '~'
	percentByte = '%'
)

View on GitHub (pinned to 0c51461d27)