{"record":{"id":"e757debf3b5a3a33","repo":"hashicorp/nomad","slug":"given-no-jobs-to-deregister","errorCode":null,"errorMessage":"given no jobs to deregister","messagePattern":"given no jobs to deregister","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"nomad/job_endpoint.go","lineNumber":916,"sourceCode":"\t}\n\tdefer metrics.MeasureSince([]string{\"nomad\", \"job\", \"batch_deregister\"}, time.Now())\n\n\taclObj, err := j.srv.ResolveACL(args)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// This RPC endpoint is only called from a Nomad server using the leader\n\t// ACL when performing garbage collection, therefore we can get away with a\n\t// simple management check to ensure the caller can trigger this\n\t// functionality as the leader token is always a management token.\n\tif !aclObj.IsManagement() {\n\t\treturn structs.ErrPermissionDenied\n\t}\n\n\t// Validate the arguments\n\tif len(args.Jobs) == 0 {\n\t\treturn fmt.Errorf(\"given no jobs to deregister\")\n\t}\n\n\t// Commit this update via Raft\n\t_, index, err := j.srv.raftApply(structs.JobBatchDeregisterRequestType, args)\n\tif err != nil {\n\t\tj.logger.Error(\"batch deregister failed\", \"error\", err)\n\t\treturn err\n\t}\n\n\treply.Index = index\n\treturn nil\n}\n\n// Scale is used to modify one of the scaling targets in the job\nfunc (j *Job) Scale(args *structs.JobScaleRequest, reply *structs.JobRegisterResponse) error {\n\tauthErr := j.srv.Authenticate(j.ctx, args)\n\tif done, err := j.srv.forward(\"Job.Scale\", args, args, reply); done {\n\t\treturn err","sourceCodeStart":898,"sourceCodeEnd":934,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/job_endpoint.go#L898-L934","documentation":"Job.BatchDeregister removes multiple jobs in one Raft transaction, but only management-level ACL tokens may use it, and the request must contain at least one job. An empty Jobs slice is rejected because there is nothing to do — Nomad refuses to commit a no-op batch.","triggerScenarios":"POST /v1/jobs/batch-deregister with a JobBatchDeregisterRequest whose Jobs map is empty or nil, typically built dynamically from a filtered list where every entry was filtered out.","commonSituations":"Cleanup automation whose job-name filter matched nothing; deserialized JSON missing the 'Jobs' key; scripts that always call the endpoint regardless of collected targets.","solutions":["Check len(jobs) > 0 before calling BatchDeregister and skip the call otherwise","Fix the filter/selection logic that produced an empty job set","Fall back to individual Job.Deregister calls per job instead of the batch endpoint","If using JSON payloads, ensure the Jobs field is populated in the request body"],"exampleFix":"// before\nreq := &api.JobBatchDeregisterRequest{Jobs: jobs}\nclient.Jobs().BatchDeregister(req, nil)\n// after\nif len(jobs) == 0 { return nil }\nreq := &api.JobBatchDeregisterRequest{Jobs: jobs}\nclient.Jobs().BatchDeregister(req, nil)","handlingStrategy":"validation","validationCode":"if len(jobs) == 0 {\n\treturn nil // nothing to deregister; skip the batch call\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Short-circuit when the target job set is empty","Verify the selection filter actually matched jobs","Prefer per-job Deregister for small sets"],"tags":["nomad","validation","batch","empty-input"],"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"}