{"id":"ec51fc6fc8f0722e","repo":"gohugoio/hugo","slug":"invalid-timeout-for-resource-s-w","errorCode":null,"errorMessage":"invalid timeout for resource %s: %w","messagePattern":"invalid timeout for resource (.+?): %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"resources/resource_factories/create/remote.go","lineNumber":215,"sourceCode":"\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse URL for resource %s: %w\", uri, err)\n\t}\n\n\tmethod := \"GET\"\n\tif s, _, ok := hmaps.LookupEqualFold(optionsm, \"method\"); ok {\n\t\tmethod = strings.ToUpper(s.(string))\n\t}\n\tisHeadMethod := method == \"HEAD\"\n\n\toptionsm = maps.Clone(optionsm)\n\n\t// Extract timeout before computing cache keys: it only affects fetch behaviour,\n\t// not the cached content, so it must not influence the cache key.\n\tvar perRequestTimeout time.Duration\n\tif v, k, ok := hmaps.LookupEqualFold(optionsm, \"timeout\"); ok {\n\t\td, err := cast.ToDurationE(v)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid timeout for resource %s: %w\", uri, err)\n\t\t}\n\t\tperRequestTimeout = d\n\t\tdelete(optionsm, k)\n\t}\n\n\tuserKey, optionsKey := remoteResourceKeys(uri, optionsm)\n\n\t// A common pattern is to use the key in the options map as\n\t// a way to control cache eviction,\n\t// so make sure we use any user provided key as the file cache key,\n\t// but the auto generated and more stable key for everything else.\n\tfilecacheKey := userKey\n\n\treturn c.rs.ResourceCache.CacheResourceRemote.GetOrCreate(optionsKey, func(key string) (resource.Resource, error) {\n\t\toptions, err := decodeRemoteOptions(optionsm)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode options for resource %s: %w\", uri, err)\n\t\t}","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/resources/resource_factories/create/remote.go#L197-L233","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"{{/* before */}}\n{{ $r := resources.GetRemote $url (dict \"timeout\" \"30 secs\") }}\n{{/* after */}}\n{{ $r := resources.GetRemote $url (dict \"timeout\" \"30s\") }}","handlingStrategy":"validation","validationCode":"{{/* timeout must be a valid Go duration string or milliseconds */}}\n{{ $opts := dict \"timeout\" \"30s\" }}\n{{ $r := resources.GetRemote $url $opts }}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["hugo","get-remote","timeout","options","duration"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}