thanos-io/thanos · error
invalid duration
Error message
invalid duration
What it means
Thrown by the Unmarshal method of queryrange.Duration (a gogo-protobuf generated wrapper around time.Duration) when the incoming value is neither a number/protobuf duration nor a valid string form. The decoder falls into the `default` branch of a type switch, meaning the JSON/YAML field holding a duration had an unrecognized type (e.g. an object, bool, or malformed scalar).
Solutions
- Send the duration as a numeric nanosecond value or a quoted string like "1m"/"500ms"
- Check the JSON payload's Content-Type and body for type errors in duration fields
- Update the client SDK/serializer to emit protobuf-compatible duration encodings
Example fix
// before
{"query":"up","step":five-minutes}
// after
{"query":"up","step":"5m"} Defensive patterns
Strategy: validation
Validate before calling
func validDuration(v interface{}) bool {
switch t := v.(type) {
case float64:
return t >= 0
case string:
_, err := time.ParseDuration(t)
return err == nil
}
return false
} Type guard
if d, ok := raw.(string); !ok { return errors.New("duration must be string") } Try / catch
if err := json.Unmarshal(body, &req); err != nil {
if strings.Contains(err.Error(), "invalid duration") {
return http.StatusBadRequest("duration fields must be numeric ns or quoted like \"5m\"")
}
} Prevention
- Always serialize durations as strings ("1m", "500ms") or nanosecond numbers
- Add schema validation (JSON schema/OpenAPI) on duration fields before sending
- Test client payloads against the API's duration decoder
When it happens
Trigger: Deserializing a query-range request or config where a duration field (step, interval, timeout) is supplied as a non-numeric, non-string JSON value — e.g. `"step": true`, `"step": {}`, or a string protobuf cannot parse as a duration.
Common situations: Hand-written JSON bodies posted to the query-frontend API; templated configs where a duration placeholder renders as a wrong type; clients sending step as a bare word like `step` instead of `"1m"`.
Understand the failure class
Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.
Related errors
- bad cached type
- proto: wrong wireType =
- proto: wrong wireType =
- proto: wrong wireType =
- proto: wrong wireType =
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/be073f10108fe10a.
Report an issue: GitHub.
Appendix: source
Thrown at internal/cortex/querier/queryrange/query_range.go:955
func (d *Duration) UnmarshalJSON(b []byte) error {
var v any
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
*d = Duration(time.Duration(value))
return nil
case string:
tmp, err := time.ParseDuration(value)
if err != nil {
return err
}
*d = Duration(tmp)
return nil
default:
return errors.New("invalid duration")
}
}
func (d *Duration) Size() int {
return github_com_gogo_protobuf_types.SizeOfStdDuration(time.Duration(*d))
}
func (d *Duration) Unmarshal(b []byte) error {
var td time.Duration
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&td, b); err != nil {
return err
}
*d = Duration(td)
return nil
}
func (d *Duration) MarshalTo(b []byte) (int, error) {
return github_com_gogo_protobuf_types.StdDurationMarshalTo(time.Duration(*d), b)View on GitHub (pinned to 35b8b99117)