{"record":{"id":"7dfb2e11b49536e4","repo":"hashicorp/nomad","slug":"job-submission-requires-a-jobid","errorCode":null,"errorMessage":"job_submission requires a jobID","messagePattern":"job_submission requires a jobID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/state/state_store.go","lineNumber":5799,"sourceCode":"\n\treturn nil\n}\n\n// updateJobSubmission stores the original job source and variables associated that the\n// job structure originates from. It is up to the job submitter to include the source\n// material, and as such sub may be nil, in which case nothing is stored.\nfunc (s *StateStore) updateJobSubmission(index uint64, sub *structs.JobSubmission, namespace, jobID string, version uint64, txn *txn) error {\n\t// critical that we operate on a copy; the original must not be modified\n\t// e.g. in the case of job gc and its last second version bump\n\tsub = sub.Copy()\n\n\tswitch {\n\tcase sub == nil:\n\t\treturn nil\n\tcase namespace == \"\":\n\t\treturn errors.New(\"job_submission requires a namespace\")\n\tcase jobID == \"\":\n\t\treturn errors.New(\"job_submission requires a jobID\")\n\tdefault:\n\t\tsub.Namespace = namespace\n\t\tsub.JobID = jobID\n\t\tsub.JobModifyIndex = index\n\t\tsub.Version = version\n\t}\n\n\t// check if we already have a submission for this (namespace, jobID, version)\n\tobj, err := txn.First(\"job_submission\", \"id\", namespace, jobID, version)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif obj != nil {\n\t\t// if we already have a submission for this (namespace, jobID, version)\n\t\t// then there is nothing to do; manually avoid potential for duplicates\n\t\treturn nil\n\t}\n","sourceCodeStart":5781,"sourceCodeEnd":5817,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/state/state_store.go#L5781-L5817","documentation":"Internal invariant guard in StateStore.updateJobSubmission: the function was invoked with an empty jobID. Storing a job's original HCL submission requires the namespace/jobID/version key; a missing jobID means the caller (job registration path) passed invalid state-store arguments.","triggerScenarios":"Calling the job submission state-store setter with jobID=\"\" — e.g. writing a submission object detached from its parent job, or a caller that has the submission struct but lost the job ID variable (empty string from parsing/decoding failure).","commonSituations":"Embedded/custom writers that call the state store directly; deserialization bugs where job.ID is empty; refactored code paths where the job ID argument is accidentally passed as an empty string.","solutions":["Pass the job's ID explicitly (sub.JobID or job.ID) when calling the state store setter","Validate jobID != \"\" before invoking the write; fail fast at the caller","If the job ID comes from decoded input, canonicalize the Job struct first so ID is populated"],"exampleFix":"// before\nstateStore.UpsertJobSubmission(msgType, idx, txn, sub, ns, \"\", index, version)\n// after\nif jobID == \"\" { jobID = sub.JobID }\nif jobID == \"\" { return errors.New(\"jobID required\") }\nstateStore.UpsertJobSubmission(msgType, idx, txn, sub, ns, jobID, index, version)","handlingStrategy":"validation","validationCode":"if jobID == \"\" {\n    if sub != nil { jobID = sub.JobID }\n    if jobID == \"\" { return errors.New(\"jobID is required for job_submission\") }\n}","typeGuard":"func hasJobID(sub *structs.JobSubmission) bool {\n    return sub != nil && sub.JobID != \"\"\n}","tryCatchPattern":"if err := upsertSubmission(sub, ns, jobID, idx); err != nil && strings.Contains(err.Error(), \"requires a jobID\") {\n    return fmt.Errorf(\"submission write dropped: ensure job.ID is set before upsert: %w\", err)\n}","preventionTips":["Validate job.ID is populated right after decoding request payloads","Pass the Job struct's ID directly instead of separate (possibly empty) variables","Add unit tests asserting submission writes always carry jobID"],"tags":["nomad","state-store","job-submission","validation"],"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"}