{"record":{"id":"ac7c7c52ae848f49","repo":"hashicorp/nomad","slug":"s3-timeout-must-be-0","errorCode":null,"errorMessage":"s3_timeout must be > 0","messagePattern":"s3_timeout must be > 0","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/config/artifact.go","lineNumber":217,"sourceCode":"\t\treturn fmt.Errorf(\"git_timeout must be > 0\")\n\t}\n\n\tif a.HgTimeout == nil {\n\t\treturn fmt.Errorf(\"hg_timeout must be set\")\n\t}\n\tif v, err := time.ParseDuration(*a.HgTimeout); err != nil {\n\t\treturn fmt.Errorf(\"hg_timeout not a valid duration: %w\", err)\n\t} else if v < 0 {\n\t\treturn fmt.Errorf(\"hg_timeout must be > 0\")\n\t}\n\n\tif a.S3Timeout == nil {\n\t\treturn fmt.Errorf(\"s3_timeout must be set\")\n\t}\n\tif v, err := time.ParseDuration(*a.S3Timeout); err != nil {\n\t\treturn fmt.Errorf(\"s3_timeout not a valid duration: %w\", err)\n\t} else if v < 0 {\n\t\treturn fmt.Errorf(\"s3_timeout must be > 0\")\n\t}\n\n\tif a.DecompressionFileCountLimit == nil {\n\t\treturn fmt.Errorf(\"decompression_file_count_limit must not be nil\")\n\t}\n\tif v := *a.DecompressionFileCountLimit; v < 0 {\n\t\treturn fmt.Errorf(\"decompression_file_count_limit must be >= 0 but found %d\", v)\n\t}\n\n\tif a.DecompressionSizeLimit == nil {\n\t\treturn fmt.Errorf(\"decompression_size_limit must not be nil\")\n\t}\n\tif v, err := humanize.ParseBytes(*a.DecompressionSizeLimit); err != nil {\n\t\treturn fmt.Errorf(\"decompression_size_limit is not a valid size: %w\", err)\n\t} else if v > math.MaxInt64 {\n\t\treturn fmt.Errorf(\"decompression_size_limit must be < %d but found %d\", int64(math.MaxInt64), v)\n\t}\n","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/config/artifact.go#L199-L235","documentation":"Validate() rejects negative s3_timeout values: after nil and parse checks succeed, `v < 0` triggers 's3_timeout must be > 0'. Despite the message wording, the comparison accepts zero; only strictly negative durations fail.","triggerScenarios":"Calling ArtifactConfig.Validate() when *a.S3Timeout parses successfully as a negative duration, e.g. \"-1m\", \"-30s\", or \"-500ms\".","commonSituations":"Configs where a negative value was intended to mean 'unlimited' or 'disabled'; arithmetic on durations producing negatives; copy/paste of a value including a minus sign; CI scripts interpolating negative default offsets.","solutions":["Set s3_timeout to a non-negative duration, e.g. s3_timeout = \"30m\".","Omit the field and let DefaultArtifactConfig() provide the default S3 timeout.","Clamp the value at generation time: if d < 0 { d = defaultS3Timeout }."],"exampleFix":"// before\nartifact {\n  s3_timeout = \"-1h\"\n}\n// after\nartifact {\n  s3_timeout = \"1h\"\n}","handlingStrategy":"validation","validationCode":"d, err := time.ParseDuration(cfg.S3Timeout)\nif err != nil || d < 0 {\n    return fmt.Errorf(\"s3_timeout must be a non-negative duration, got %q\", cfg.S3Timeout)\n}","typeGuard":"func validS3Timeout(a *ArtifactConfig) bool {\n    if a.S3Timeout == nil { return false }\n    d, err := time.ParseDuration(*a.S3Timeout)\n    return err == nil && d >= 0\n}","tryCatchPattern":null,"preventionTips":["Use zero or omit the field instead of negative values for 'no limit' semantics.","Clamp computed durations before serializing config.","Review templated configs that do duration arithmetic."],"tags":["nomad","config-validation","duration","s3"],"backgroundTag":"invalid-duration-config","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}