hashicorp/nomad · error
variable doesn't hold a lock
Error message
variable doesn't hold a lock
What it means
errLockNotFound is a sentinel error in Nomad's state store for Variables. It is returned when a variable update operation (such as a lock release or lock renewal) references a variable whose Lock field is nil or whose Lock.ID is empty — i.e. the variable exists but does not currently hold a lock, so there is nothing to release or renew.
Source
Thrown at nomad/state/state_store_variables.go:18
// Copyright IBM Corp. 2015, 2026
// SPDX-License-Identifier: BUSL-1.1
package state
import (
"errors"
"fmt"
"math"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/nomad/nomad/structs"
)
var (
errVarAlreadyLocked = errors.New("variable already holds a lock")
errVarNotFound = errors.New("variable doesn't exist")
errLockNotFound = errors.New("variable doesn't hold a lock")
)
// Variables queries all the variables and is used only for
// snapshot/restore and key rotation
func (s *StateStore) Variables(ws memdb.WatchSet) (memdb.ResultIterator, error) {
txn := s.db.ReadTxn()
iter, err := txn.Get(TableVariables, indexID)
if err != nil {
return nil, err
}
ws.Add(iter.WatchCh())
return iter, nil
}
// GetVariablesByNamespace returns an iterator that contains all
// variables belonging to the provided namespace.View on GitHub (pinned to 482b49bf1a)
Solutions
- Check that the variable holds a lock before issuing a release/renew operation
- Re-fetch the variable via the Variables API to confirm its current lock state
- If the lock was already released, treat this as idempotent success in the caller
- Ensure the lock was created via VarLock before attempting lock lifecycle operations
Example fix
// before
store.VarLockRelease(1000, path, lockID) // panics/errors if no lock
// after
sv, _ := store.VariablesByID(nil, path)
if sv != nil && sv.Lock != nil && sv.Lock.ID == lockID {
store.VarLockRelease(1000, path, lockID)
} Defensive patterns
Strategy: validation
Validate before calling
func canReleaseOrRenew(sv *structs.Variable, lockID string) bool {
return sv != nil && sv.Lock != nil && sv.Lock.ID != "" && sv.Lock.ID == lockID
}
if !canReleaseOrRenew(sv, myLockID) { /* skip or refetch variable */ } Type guard
func holdsLock(sv *structs.Variable) bool {
return sv != nil && sv.Lock != nil && sv.Lock.ID != ""
} Try / catch
if err := store.VarLockRelease(idx, path, lockID); errors.Is(err, errLockNotFound) {
// lock already gone; treat as idempotent success or refetch
return nil
} Prevention
- Always create the lock via VarLock before releasing/renewing
- Refetch the variable to check Lock state before lock operations
- Treat lock release as idempotent in callers
- Watch for concurrent releases that can race your operation
When it happens
Trigger: Calling VarLockRelease or RenewLock (or the matching RPCs against the state store) on a variable that has no lock attached; VarLock/Delete ops in a state-store apply where sv.Lock == nil || sv.Lock.ID == "".
Common situations: A client lost the lock (it was released or force-cleared by another process) and later tries to release/renew it again using stale session data; double-release after a failed write; tests simulating a variable at path without a lock.
Related errors
- variable already holds a lock
- variable doesn't exist
- missing lock ID
- can not target wildcard ("*")namespace
- namespace %q contains at least one variable. All variables i
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/38b654d7f17a0dd2.
Report an issue: GitHub.