{"record":{"id":"67f59256da245f6a","repo":"hashicorp/nomad","slug":"hg-timeout-must-be-0","errorCode":null,"errorMessage":"hg_timeout must be > 0","messagePattern":"hg_timeout must be > 0","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/config/artifact.go","lineNumber":208,"sourceCode":"\t\treturn fmt.Errorf(\"gcs_timeout must be > 0\")\n\t}\n\n\tif a.GitTimeout == nil {\n\t\treturn fmt.Errorf(\"git_timeout must be set\")\n\t}\n\tif v, err := time.ParseDuration(*a.GitTimeout); err != nil {\n\t\treturn fmt.Errorf(\"git_timeout not a valid duration: %w\", err)\n\t} else if v < 0 {\n\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","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/config/artifact.go#L190-L226","documentation":"Nomad's ArtifactConfig.Validate requires hg_timeout to be set and rejects negative durations. A negative value passed the nil and ParseDuration checks but failed the `v < 0` comparison, so the Mercurial artifact-download timeout is invalid. Only zero and positive durations are accepted (zero meaning no explicit timeout beyond defaults).","triggerScenarios":"Calling ArtifactConfig.Validate() when a.HgTimeout is a non-nil pointer to a duration string that time.ParseDuration accepts but is negative, e.g. *a.HgTimeout == \"-30s\" or \"-5m\".","commonSituations":"Hand-written client or agent hcl/json artifact blocks where a minus sign was typed accidentally; templated config that interpolates a negative computed offset; config copied from tooling that treats -1 as 'disable timeout' (Nomad does not).","solutions":["Change hg_timeout in the artifact block to a positive duration, e.g. hg_timeout = \"30m\".","Remove the explicit hg_timeout so DefaultArtifactConfig() supplies a valid default.","If hg_timeout is computed from a variable, clamp it with a max(0, v) or abs() before formatting the duration string."],"exampleFix":"// before\nartifact {\n  hg_timeout = \"-30s\"\n}\n// after\nartifact {\n  hg_timeout = \"30s\"\n}","handlingStrategy":"validation","validationCode":"d, err := time.ParseDuration(cfg.HgTimeout)\nif err != nil || d < 0 {\n    return fmt.Errorf(\"hg_timeout must be a non-negative Go duration, got %q\", cfg.HgTimeout)\n}","typeGuard":"func validHgTimeout(a *ArtifactConfig) bool {\n    if a.HgTimeout == nil { return false }\n    d, err := time.ParseDuration(*a.HgTimeout)\n    return err == nil && d >= 0\n}","tryCatchPattern":null,"preventionTips":["Never use negative durations to mean 'unlimited' in Nomad configs.","Unit-test config builders against ArtifactConfig.Validate().","Run `nomad agent config validate` (or job validate) before deploying config changes."],"tags":["nomad","config-validation","duration","mercurial"],"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-08T10:18:20.063Z"}