{"record":{"id":"e4256127085657b4","repo":"hashicorp/nomad","slug":"job-required-for-plan","errorCode":null,"errorMessage":"Job required for plan","messagePattern":"Job required for plan","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/job_endpoint.go","lineNumber":1777,"sourceCode":"\treturn nil\n}\n\n// Plan is used to cause a dry-run evaluation of the Job and return the results\n// with a potential diff containing annotations.\nfunc (j *Job) Plan(args *structs.JobPlanRequest, reply *structs.JobPlanResponse) error {\n\tauthErr := j.srv.Authenticate(j.ctx, args)\n\tif done, err := j.srv.forward(\"Job.Plan\", args, args, reply); done {\n\t\treturn err\n\t}\n\tj.srv.MeasureRPCRate(\"job\", structs.RateMetricWrite, args)\n\tif authErr != nil {\n\t\treturn structs.ErrPermissionDenied\n\t}\n\tdefer metrics.MeasureSince([]string{\"nomad\", \"job\", \"plan\"}, time.Now())\n\n\t// Validate the arguments\n\tif args.Job == nil {\n\t\treturn fmt.Errorf(\"Job required for plan\")\n\t}\n\n\t// Run admission controllers\n\tjob, warnings, err := j.admissionControllers(args.Job)\n\tif err != nil {\n\t\treturn err\n\t}\n\targs.Job = job\n\n\t// Set the warning message\n\treply.Warnings = helper.MergeMultierrorWarnings(warnings...)\n\n\t// Check job submission permissions, which we assume is the same for plan\n\tif aclObj, err := j.srv.ResolveACL(args); err != nil {\n\t\treturn err\n\t} else {\n\t\tif !aclObj.AllowNsOpAnyOf(args.RequestNamespace(),\n\t\t\tacl.NamespaceCapabilitySubmitJob,","sourceCodeStart":1759,"sourceCodeEnd":1795,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/job_endpoint.go#L1759-L1795","documentation":"Job.Plan was called without a Job payload. The plan endpoint evaluates a proposed job against the cluster without registering it, so a nil args.Job means there is nothing to evaluate; the request is rejected with 'Job required for plan'.","triggerScenarios":"POST /v1/job/plan with an empty or missing Job object in the body (client.Jobs().Plan(nil, true, nil)) after ACL checks, at nomad/job_endpoint.go:1777.","commonSituations":"Dry-run tooling serializing a nil job; HCL/JSON job spec that failed to parse into a structs.Job yet the code proceeded; calling the raw endpoint with an empty POST body.","solutions":["Supply a fully parsed structs.Job in the request body before calling Plan.","Run 'nomad job validate' / the parse API on the HCL first and abort on parse failure.","Check that the config file actually loaded (empty file or wrong path yields a nil/empty job).","Construct the job programmatically and assert non-nil before submitting the plan request."],"exampleFix":"// before\njob, err := jobspec.Parse(f) // err ignored, job nil\nplan, _, err := client.Jobs().Plan(job, true, nil)\n// after\njob, err := jobspec.Parse(f)\nif err != nil { return err }\nif job == nil { return errors.New(\"parsed job is nil\") }\nplan, _, err := client.Jobs().Plan(job, true, nil)","handlingStrategy":"validation","validationCode":"if job == nil {\n    return fmt.Errorf(\"nothing to plan: parsed job is nil\")\n}\nif _, _, err := client.Jobs().Validate(job, nil); err != nil { return err }\nplan, _, err := client.Jobs().Plan(job, diff, nil)","typeGuard":"func jobProvided(j *api.Job) bool { return j != nil && j.ID != nil && *j.ID != \"\" }","tryCatchPattern":null,"preventionTips":["Check errors from job parsing before building plan requests.","Assert non-nil job after file loads and template expansion.","Dry-run via the validate endpoint before the plan endpoint."],"tags":["nomad","request-validation","missing-parameter"],"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"}