hashicorp/nomad · error
job not found
Error message
job not found
What it means
StateStore.DeleteJobTxn (nomad/state/state_store.go:1969) returns this when the jobs table has no row for (namespace, jobID) at delete time. Deregistering a job that the state store cannot find is treated as an application-level error and the transaction aborts with 'job not found'.
Source
Thrown at nomad/state/state_store.go:1969
defer txn.Abort()
err := s.DeleteJobTxn(index, namespace, jobID, txn)
if err == nil {
return txn.Commit()
}
return err
}
// DeleteJobTxn is used to deregister a job, like DeleteJob,
// but in a transaction. Useful for when making multiple modifications atomically
func (s *StateStore) DeleteJobTxn(index uint64, namespace, jobID string, txn Txn) error {
// Lookup the node
existing, err := txn.First("jobs", "id", namespace, jobID)
if err != nil {
return fmt.Errorf("job lookup failed: %v", err)
}
if existing == nil {
return fmt.Errorf("job not found")
}
// Check if we should update a parent job summary
job := existing.(*structs.Job)
if job.ParentID != "" {
summaryRaw, err := txn.First("job_summary", "id", namespace, job.ParentID)
if err != nil {
return fmt.Errorf("unable to retrieve summary for parent job: %v", err)
}
// Only continue if the summary exists. It could not exist if the parent
// job was removed
if summaryRaw != nil {
existing := summaryRaw.(*structs.JobSummary)
pSummary := existing.Copy()
if pSummary.Children != nil {
modified := falseView on GitHub (pinned to 482b49bf1a)
Solutions
- Verify the job ID and namespace exist first (nomad job status <id> / GET /v1/job/<id> in that namespace)
- Treat 'job not found' as idempotent success if the goal is best-effort deregistration
- Check whether GC or another process already deleted the job before re-submitting
- Confirm you are pointed at the correct region/cluster
Example fix
// before
err := s.DeleteJobTxn(idx, ns, jobID, txn)
return err
// after
if err := s.DeleteJobTxn(idx, ns, jobID, txn); err != nil {
if err.Error() == "job not found" {
return nil // idempotent delete
}
return err
} Defensive patterns
Strategy: validation
Validate before calling
// confirm the job exists before deregistering
status, _, err := client.Jobs().Info(jobID, &api.QueryOptions{Namespace: ns})
if err != nil || status == nil {
return nil // job absent; skip delete instead of triggering 'job not found'
} Type guard
func jobPresent(raw interface{}) bool { return raw != nil } // mirrors txn.First nil check Try / catch
if err := s.DeleteJobTxn(idx, ns, jobID, txn); err != nil {
if err.Error() == "job not found" {
return nil // treat as idempotent success
}
return err
} Prevention
- Always check job ID and namespace spelling before deregistering
- Make deregistration idempotent — a second delete racing the first gets 'job not found'
- Remember GC may already have purged stopped jobs
- Target the correct region/cluster and namespace
When it happens
Trigger: Job.Deregister RPC for a jobID/namespace combination that was never registered, or that was already deleted (double deregister, GC already purged it).
Common situations: Typo in job ID or wrong -namespace; two concurrent deregisters racing so the second finds nothing; job already removed by garbage collection after being stopped; deregistering against the wrong region/cluster.
Related errors
- deployment %q references unknown job %q
- error parsing: root should be an object
- cannot specify Accessor ID
- network already configured but not found in state
- Group name required
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/c429daeb13172c9d.
Report an issue: GitHub.