{"record":{"id":"3f7e50a052b2ee66","repo":"hashicorp/nomad","slug":"stop-after-cannot-be-a-negative-duration","errorCode":null,"errorMessage":"stop_after cannot be a negative duration","messagePattern":"stop_after cannot be a negative duration","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/group.go","lineNumber":28,"sourceCode":"\n\t\"github.com/hashicorp/go-multierror\"\n)\n\nconst (\n\t// ReconcileOption is used to specify the behavior of the reconciliation process\n\t// between the original allocations and the replacements when a previously\n\t// disconnected client comes back online.\n\tReconcileOptionKeepOriginal    = \"keep_original\"\n\tReconcileOptionKeepReplacement = \"keep_replacement\"\n\tReconcileOptionBestScore       = \"best_score\"\n\tReconcileOptionLongestRunning  = \"longest_running\"\n)\n\nvar (\n\t// Disconnect strategy validation errors\n\terrStopAndLost         = errors.New(\"Disconnect cannot be configured with both lost_after and stop_after\")\n\terrNegativeLostAfter   = errors.New(\"lost_after cannot be a negative duration\")\n\terrNegativeStopAfter   = errors.New(\"stop_after cannot be a negative duration\")\n\terrStopAfterNonService = errors.New(\"stop_after can only be used with service or batch job types\")\n\terrInvalidReconcile    = errors.New(\"reconcile option is invalid\")\n)\n\nfunc NewDefaultDisconnectStrategy() *DisconnectStrategy {\n\treturn &DisconnectStrategy{\n\t\tReplace:   new(true),\n\t\tReconcile: ReconcileOptionBestScore,\n\t}\n}\n\n// Disconnect strategy defines how both clients and server should behave in case of\n// disconnection between them.\ntype DisconnectStrategy struct {\n\t// Defines for how long the server will consider the unresponsive node as\n\t// disconnected but alive instead of lost.\n\tLostAfter time.Duration `mapstructure:\"lost_after\" hcl:\"lost_after,optional\"`\n","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/group.go#L10-L46","documentation":"errNegativeStopAfter is a disconnect-strategy validation error: stop_after must be zero or a positive duration. A negative stop_after would mean stopping the task before the disconnect happens, so DisconnectStrategy.Validate rejects the job when *ds.StopOnClientAfter < 0.","triggerScenarios":"Submitting a job whose group disconnect block sets stop_after to a negative duration, checked at group.go:75 in DisconnectStrategy.Validate (only when StopOnClientAfter is non-nil).","commonSituations":"Typos like stop_after = \"-30m\"; templated durations computed from data that can be negative; mixing up sign when converting between seconds/nanoseconds in generated job specs.","solutions":["Set stop_after to a positive duration (e.g. \"30m\") or remove it to disable stop-on-disconnect","Clamp or abs() any computed duration used in the job template","Validate the job with 'nomad job validate' before submission"],"exampleFix":"// before\ndisconnect {\n  stop_after = \"-30m\"\n}\n// after\ndisconnect {\n  stop_after = \"30m\"\n}","handlingStrategy":"validation","validationCode":"if ds := grp.Disconnect; ds != nil && ds.StopOnClientAfter != nil && *ds.StopOnClientAfter < 0 {\n    return fmt.Errorf(\"disconnect: stop_after must be >= 0\")\n}","typeGuard":"func stopAfterValid(ds *api.DisconnectStrategy) bool {\n    return ds == nil || ds.StopOnClientAfter == nil || *ds.StopOnClientAfter >= 0\n}","tryCatchPattern":"err := client.Jobs().Validate(job, nil, nil)\nif err != nil && strings.Contains(err.Error(), \"stop_after cannot be a negative duration\") {\n    return fmt.Errorf(\"fix stop_after to a positive duration: %w\", err)\n}","preventionTips":["Clamp or abs() computed durations before templating stop_after","Add CI validation via 'nomad job validate' for all job specs","Use explicit positive literals for stop_after instead of derived values when possible"],"tags":["nomad","job-spec","validation","disconnect"],"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"}