gohugoio/hugo · error
invalid timeout for resource %s: %w
Error message
invalid timeout for resource %s: %w
What it means
`resources.GetRemote` accepts a per-request `timeout` option which is converted with `cast.ToDurationE`. If the provided value cannot be cast to a Go `time.Duration` — e.g. a string without a unit that cast rejects, or a non-duration type — Hugo returns this error before making any request. The timeout is deliberately extracted before cache-key computation because it affects only fetch behavior.
Source
Thrown at resources/resource_factories/create/remote.go:215
if err != nil {
return nil, fmt.Errorf("failed to parse URL for resource %s: %w", uri, err)
}
method := "GET"
if s, _, ok := hmaps.LookupEqualFold(optionsm, "method"); ok {
method = strings.ToUpper(s.(string))
}
isHeadMethod := method == "HEAD"
optionsm = maps.Clone(optionsm)
// Extract timeout before computing cache keys: it only affects fetch behaviour,
// not the cached content, so it must not influence the cache key.
var perRequestTimeout time.Duration
if v, k, ok := hmaps.LookupEqualFold(optionsm, "timeout"); ok {
d, err := cast.ToDurationE(v)
if err != nil {
return nil, fmt.Errorf("invalid timeout for resource %s: %w", uri, err)
}
perRequestTimeout = d
delete(optionsm, k)
}
userKey, optionsKey := remoteResourceKeys(uri, optionsm)
// A common pattern is to use the key in the options map as
// a way to control cache eviction,
// so make sure we use any user provided key as the file cache key,
// but the auto generated and more stable key for everything else.
filecacheKey := userKey
return c.rs.ResourceCache.CacheResourceRemote.GetOrCreate(optionsKey, func(key string) (resource.Resource, error) {
options, err := decodeRemoteOptions(optionsm)
if err != nil {
return nil, fmt.Errorf("failed to decode options for resource %s: %w", uri, err)
}View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Use a valid Go duration string or integer, e.g. `dict "timeout" "30s"` or `dict "timeout" 30000000000` (nanoseconds) — prefer the string form.
- Check the source of the value (site params, data file) and ensure it's a scalar string/number, not a nested structure.
- If no per-request timeout is needed, remove the key and rely on the global `timeout` in site config.
Example fix
{{/* before */}}
{{ $r := resources.GetRemote $url (dict "timeout" "30 secs") }}
{{/* after */}}
{{ $r := resources.GetRemote $url (dict "timeout" "30s") }} Defensive patterns
Strategy: validation
Validate before calling
{{/* timeout must be a valid Go duration string or milliseconds */}}
{{ $opts := dict "timeout" "30s" }}
{{ $r := resources.GetRemote $url $opts }} Prevention
- Use Go duration syntax ("30s", "1m") for the timeout option, not bare unsuffixed strings like "30" unless you mean the numeric form the docs allow
- If the timeout comes from site params, validate it centrally with time.ParseDuration semantics before use
- Prefer the site-wide `timeout` config over per-call overrides so there is one value to validate
When it happens
Trigger: Passing an options dict to `resources.GetRemote` with a `timeout` key whose value isn't a valid duration, e.g. `dict "timeout" "30x"`, an empty string, or a malformed string like `"30 s"`; the key is matched case-insensitively so `Timeout`/`TIMEOUT` also hit this path.
Common situations: Writing `timeout "30"`-style values assuming seconds when a bare-number string may not cast, typos in the unit (`"5sec"` instead of `"5s"`), or piping a config/front-matter value that is unexpectedly a slice or map.
Related errors
- failed to decode options for resource %s: %w
- failed to read remote resource %q: %w
- invalid options type: %w
- %q is not a valid duration unit
- failed to decode options: %w
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/ec51fc6fc8f0722e.json.
Report an issue: GitHub ↗.