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 := false

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Verify the job ID and namespace exist first (nomad job status <id> / GET /v1/job/<id> in that namespace)
  2. Treat 'job not found' as idempotent success if the goal is best-effort deregistration
  3. Check whether GC or another process already deleted the job before re-submitting
  4. 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

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


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/c429daeb13172c9d. Report an issue: GitHub.