{"record":{"id":"306d9646e0883531","repo":"hashicorp/nomad","slug":"errlockconflict","errorCode":"ErrLockConflict","errorMessage":"conflicting operation over lock","messagePattern":"conflicting operation over lock","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/locks.go","lineNumber":32,"sourceCode":"\nconst (\n\tlockLeaseRenewalFactor = 0.7\n\tlockRetryBackoffFactor = 1.1\n\n\t// DefaultLockTTL is the default value used to maintain a lock before it needs to\n\t// be renewed. The actual value comes from the experience with Consul.\n\tDefaultLockTTL = 15 * time.Second\n\n\t// DefaultLockDelay is the default a lock will be blocked after the TTL\n\t// went by without any renews. It is intended to prevent split brain situations.\n\t// The actual value comes from the experience with Consul.\n\tDefaultLockDelay = 15 * time.Second\n)\n\nvar (\n\t// ErrLockConflict is returned in case a lock operation can't be performed\n\t// because the caller is not the current holder of the lock.\n\tErrLockConflict = errors.New(\"conflicting operation over lock\")\n\n\t//LockNoPathErr is returned when no path is provided in the variable to be\n\t// used for the lease mechanism\n\tLockNoPathErr = errors.New(\"variable's path can't be empty\")\n)\n\n// Locks returns a new handle on a lock for the given variable.\nfunc (c *Client) Locks(wo WriteOptions, v Variable, opts ...LocksOption) (*Locks, error) {\n\n\tif v.Path == \"\" {\n\t\treturn nil, LockNoPathErr\n\t}\n\n\tttl, err := time.ParseDuration(v.Lock.TTL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/api/locks.go#L14-L50","documentation":"ErrLockConflict is the sentinel error returned when a lock operation (acquire, release, renew) cannot be performed because the caller is not the current holder of the lock. The Nomad server responds HTTP 409 Conflict and the client wraps this sentinel into the returned error.","triggerScenarios":"Acquiring a lock already held by another session/instance; releasing or renewing a lock after your session expired or was invalidated; two processes contending for the same lock variable path.","commonSituations":"Split-brain or stale leader instances in a deployment tool; lock hold time exceeded and session reaped; multiple replicas starting simultaneously racing on the same lock path.","solutions":["Use errors.Is(err, api.ErrLockConflict) to detect it and retry acquire with backoff (honor DefaultLockDelay of 15s)","Check whether your Lock session is still alive before releasing/renewing; re-create the session and re-acquire if expired","Ensure only one instance/leader attempts to hold a given lock path","Inspect the lock variable's Session value via the API to identify the current holder"],"exampleFix":"// before\n_, err := lock.Acquire(q)\nif err != nil { return err }\n// after\n_, err := lock.Acquire(q)\nif errors.Is(err, api.ErrLockConflict) {\n    time.Sleep(api.DefaultLockDelay)\n    _, err = lock.Acquire(q)\n}\nif err != nil { return err }","handlingStrategy":"retry","validationCode":"func retryableLockErr(err error) bool { return errors.Is(err, api.ErrLockConflict) }","typeGuard":null,"tryCatchPattern":"_, err := lock.Acquire(nil)\nif errors.Is(err, api.ErrLockConflict) {\n    // wait and retry acquire with backoff\n    time.Sleep(api.DefaultLockDelay)\n    _, err = lock.Acquire(nil)\n}","preventionTips":["Use errors.Is (not == or string match) to detect ErrLockConflict through %w wrapping","Retry acquire with backoff honoring DefaultLockDelay (15s)","Ensure only one leader/instance holds a given lock path","Monitor session health; recreate expired sessions before release/renew"],"tags":["go","nomad","lock","conflict","http-409"],"backgroundTag":"lock-conflict","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}