hashicorp/nomad · error

node pool %s not found

Error message

node pool %s not found

What it means

deleteNodePoolTxn fails when the named node pool does not exist in the node_pools table. The lookup `txn.First(TableNodePools, "id", name)` returns nil, so the delete is rejected with this message rather than silently succeeding. Note deletion of existing non-built-in pools proceeds afterward only if the pool is not built-in and has no nodes.

Source

Thrown at nomad/state/state_store_node_pools.go:203

		}
	}

	// Update index table.
	if err := txn.Insert("index", &IndexEntry{TableNodePools, index}); err != nil {
		return fmt.Errorf("index update failed: %w", err)
	}

	return txn.Commit()
}

func (s *StateStore) deleteNodePoolTxn(txn *txn, index uint64, name string) error {
	// Check if node pool exists.
	existing, err := txn.First(TableNodePools, "id", name)
	if err != nil {
		return fmt.Errorf("node pool lookup failed: %w", err)
	}
	if existing == nil {
		return fmt.Errorf("node pool %s not found", name)
	}

	pool := existing.(*structs.NodePool)

	// Prevent deletion of built-in node pools.
	if pool.IsBuiltIn() {
		return fmt.Errorf("deleting node pool %q is not allowed", pool.Name)
	}

	// Delete node pool.
	if err := txn.Delete(TableNodePools, pool); err != nil {
		return fmt.Errorf("node pool deletion failed: %w", err)
	}

	return nil
}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Verify the pool name with `nomad node pool list` and correct any typo in the delete request.
  2. Add an existence check (GET the node pool or txn.First on the table) before deleting to make automation idempotent.
  3. Treat 'not found' as success in idempotent cleanup scripts and skip the delete.
  4. Recreate the pool first if it was deleted accidentally and the delete is part of a larger transaction.

Example fix

// before
stateStore.DeleteNodePools(idx, []string{"prod-pool"}, nil) // may not exist

// after
if pool, _ := stateStore.NodePoolByName(nil, "prod-pool"); pool != nil {
    stateStore.DeleteNodePools(idx, []string{"prod-pool"}, nil)
}
Defensive patterns

Strategy: validation

Validate before calling

existing, err := s.NodePoolByName(nil, name)
if err != nil {
    return err
}
if existing == nil {
    return nil // already gone; treat as idempotent success
}
err = s.DeleteNodePools(idx, []string{name}, nil)

Type guard

func poolExists(s *state.StateStore, name string) bool {
    p, err := s.NodePoolByName(nil, name)
    return err == nil && p != nil
}

Try / catch

err := s.DeleteNodePools(idx, []string{name}, nil)
if err != nil && strings.Contains(err.Error(), "not found") {
    return nil // idempotent: pool already deleted
}

Prevention

When it happens

Trigger: Calling DeleteNodePools with a name that was never created or has already been deleted — e.g. `nomad node pool delete <name>` for a typo'd name, or an automation deleting pools idempotently without an existence check.

Common situations: Typo'd pool name in CLI or Terraform config; racing deletions where two processes delete the same pool; environment drift where the pool exists in one cluster but not another (staging vs prod).

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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