hashicorp/nomad · error

multiple lock operations

Error message

multiple lock operations

What it means

getLockOperation in command/agent/variable_endpoint.go determines which single lock operation (renew, acquire, release) a variable request carries. If more than one of the renewLock/acquireLock/releaseLock flags is set, the request is ambiguous and the function returns this error. A variable lock request must express exactly zero or one lock operation.

Source

Thrown at command/agent/variable_endpoint.go:298

}

func isOneAndOnlyOneSet(a, b, c bool) bool {
	return (a || b || c) && !a != !b != !c != !(a && b && c)
}

// getLockOperation returns the lock operation to be performed in case there is
// one. It returns error if more than one is set.
func getLockOperation(queryParams url.Values) (string, error) {
	_, renewLock := queryParams[renewLockQueryParam]
	_, acquireLock := queryParams[acquireLockQueryParam]
	_, releaseLock := queryParams[releaseLockQueryParam]

	if !renewLock && !acquireLock && !releaseLock {
		return "", nil
	}

	if !isOneAndOnlyOneSet(renewLock, acquireLock, releaseLock) {
		return "", errors.New("multiple lock operations")
	}

	switch {
	case renewLock:
		return renewLockQueryParam, nil
	case acquireLock:
		return acquireLockQueryParam, nil
	case releaseLock:
		return releaseLockQueryParam, nil
	default:
		return "", errors.New("unspecified lock operation")
	}
}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Send only one lock operation per request: set exactly one of acquire, release, or renew and leave the others unset.
  2. Split combined operations into sequential requests (acquire first, then later renew/release).
  3. Audit client code/automation for defaults that set multiple lock booleans (e.g. `lock: true, unlock: true`).
  4. Check for zero values being explicitly serialized: omit unset lock fields instead of sending explicit false/true pairs.

Example fix

// before: ambiguous request
POST /v1/var/path?lock=true&unlock=true
// after: single operation per call
POST /v1/var/path?lock=true
POST /v1/var/path?unlock=true
Defensive patterns

Strategy: validation

Validate before calling

ops := []bool{renew, acquire, release}
count := 0
for _, o := range ops {
    if o {
        count++
    }
}
if count > 1 {
    return errors.New("send only one lock operation per variable request")
}

Try / catch

if err := updateVariable(req); err != nil {
    if strings.Contains(err.Error(), "multiple lock operations") {
        return fmt.Errorf("split request: set only one of lock/unlock/renew per call")
    }
    return err
}

Prevention

When it happens

Trigger: Issuing a variable write/update via VariableSpecificRequest with two or more lock query flags simultaneously — e.g. a request setting both `lock=true` (acquire) and `unlock=true` (release), or renew plus release in the same body/query.

Common situations: Hand-built API calls combining lock query parameters; templating bugs where a client sets all lock fields to the same boolean; retry logic re-sending a request that merges two operations; misuse of the variables HTTP API with both lock and unlock params.

Related errors


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