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

  1. Use a valid Go duration string or integer, e.g. `dict "timeout" "30s"` or `dict "timeout" 30000000000` (nanoseconds) — prefer the string form.
  2. Check the source of the value (site params, data file) and ensure it's a scalar string/number, not a nested structure.
  3. 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

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


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/ec51fc6fc8f0722e.json. Report an issue: GitHub ↗.