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
- Use the correct single-character unit: H, M, S, m, u, n. Note seconds are 'S' (uppercase) and milliseconds are 'm' (lowercase).
- Do not set grpc-timeout by hand; let gRPC derive it from the context deadline.
- 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
- Remember seconds='S' uppercase, milliseconds='m' lowercase; no multi-char units like 'ms'.
- Avoid setting grpc-timeout by hand; use context deadlines.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- transport: timeout string is too long
- transport: timeout string is too short
- received an illegal stream id
- received -bytes data exceeding the limit bytes
- ErrCodeEnhanceYourCalm
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)