{"record":{"id":"14ae893be396f28d","repo":"hashicorp/nomad","slug":"task-group-v-should-have-an-ephemeral-disk-object","errorCode":null,"errorMessage":"Task Group %v should have an ephemeral disk object","messagePattern":"Task Group (.+?) should have an ephemeral disk object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/structs.go","lineNumber":7219,"sourceCode":"\t\tif tg.ReschedulePolicy != nil {\n\t\t\tmErr = multierror.Append(mErr, fmt.Errorf(\"System or sysbatch jobs should not have a reschedule policy\"))\n\t\t}\n\t} else {\n\t\tif tg.ReschedulePolicy != nil {\n\t\t\tif err := tg.ReschedulePolicy.Validate(); err != nil {\n\t\t\t\tmErr = multierror.Append(mErr, err)\n\t\t\t}\n\t\t} else {\n\t\t\tmErr = multierror.Append(mErr, fmt.Errorf(\"Task Group %v should have a reschedule policy\", tg.Name))\n\t\t}\n\t}\n\n\tif tg.EphemeralDisk != nil {\n\t\tif err := tg.EphemeralDisk.Validate(); err != nil {\n\t\t\tmErr = multierror.Append(mErr, err)\n\t\t}\n\t} else {\n\t\tmErr = multierror.Append(mErr, fmt.Errorf(\"Task Group %v should have an ephemeral disk object\", tg.Name))\n\t}\n\n\t// Validate the update strategy\n\tif u := tg.Update; u != nil {\n\t\tswitch j.Type {\n\t\tcase JobTypeService, JobTypeSystem:\n\t\tdefault:\n\t\t\tmErr = multierror.Append(mErr, fmt.Errorf(\"Job type %q does not allow update block\", j.Type))\n\t\t}\n\t\tif err := u.Validate(); err != nil {\n\t\t\tmErr = multierror.Append(mErr, err)\n\t\t}\n\t}\n\n\t// Validate the migration strategy\n\tswitch j.Type {\n\tcase JobTypeService:\n\t\tif tg.Migrate != nil {","sourceCodeStart":7201,"sourceCodeEnd":7237,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/structs.go#L7201-L7237","documentation":"Nomad requires each task group to have an EphemeralDisk object; when `tg.EphemeralDisk == nil` during validation, this error naming the group is appended. Like the reschedule check, it typically indicates a hand-constructed job struct that never went through canonicalization, which normally populates a default ephemeral disk (migrate = true, sizeMB = 300).","triggerScenarios":"Submitting a job whose task group lacks an `ephemeral_disk` stanza and whose struct was not canonicalized — e.g. constructing `structs.TaskGroup` directly in Go tests or sending raw JSON without defaults.","commonSituations":"Go API users building TaskGroup structs manually and calling Validate without Canonicalize; minimal test fixtures; migration of very old job specs predating the ephemeral disk stanza.","solutions":["Add an `ephemeral_disk` stanza (even empty) to the task group.","Call `job.Canonicalize()` before validating/submitting when building jobs via the Go API.","Submit through the standard HTTP API/CLI so server-side canonicalization supplies defaults."],"exampleFix":"// before\ngroup \"app\" {\n  task \"app\" {}\n}\n// after\ngroup \"app\" {\n  ephemeral_disk {\n    migrate = true\n    size_mb = 300\n  }\n  task \"app\" {}\n}","handlingStrategy":"validation","validationCode":"func ensureEphemeralDisk(j *api.Job) {\n\tfor _, tg := range j.TaskGroups {\n\t\tif tg.EphemeralDisk == nil {\n\t\t\ttg.EphemeralDisk = &api.EphemeralDisk{\n\t\t\t\tSticky:  pointer.Of(false),\n\t\t\t\tMigrate: pointer.Of(true),\n\t\t\t\tSizeMB:  pointer.Of(300),\n\t\t\t}\n\t\t}\n\t}\n}","typeGuard":"func hasEphemeralDisk(tg *api.TaskGroup) bool { return tg != nil && tg.EphemeralDisk != nil }","tryCatchPattern":null,"preventionTips":["Call `job.Canonicalize()` before validating hand-built job structs.","Include an explicit `ephemeral_disk` stanza in group templates.","Reuse canonicalized fixtures in tests rather than raw struct literals."],"tags":["nomad","job-validation","ephemeral-disk","missing-config"],"backgroundTag":"missing-required-argument","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"}