{"record":{"id":"e2303acc2aef9765","repo":"hashicorp/terraform","slug":"failed-to-lock-state-in-consul-s","errorCode":null,"errorMessage":"failed to lock state in Consul: %s","messagePattern":"failed to lock state in Consul: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/consul/backend_state.go","lineNumber":108,"sourceCode":"\t}\n\n\tif !b.lock {\n\t\tstateMgr.DisableLocks()\n\t}\n\n\t// the default state always exists\n\tif name == backend.DefaultStateName {\n\t\treturn stateMgr, nil\n\t}\n\n\t// Grab a lock, we use this to write an empty state if one doesn't\n\t// exist already. We have to write an empty state as a sentinel value\n\t// so States() knows it exists.\n\tlockInfo := statemgr.NewLockInfo()\n\tlockInfo.Operation = \"init\"\n\tlockId, err := stateMgr.Lock(lockInfo)\n\tif err != nil {\n\t\treturn nil, diags.Append(fmt.Errorf(\"failed to lock state in Consul: %s\", err))\n\t}\n\n\t// Local helper function so we can call it multiple places\n\tlockUnlock := func(parent error) error {\n\t\tif err := stateMgr.Unlock(lockId); err != nil {\n\t\t\treturn fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)\n\t\t}\n\n\t\treturn parent\n\t}\n\n\t// Grab the value\n\tif err := stateMgr.RefreshState(); err != nil {\n\t\terr = lockUnlock(err)\n\t\treturn nil, diags.Append(err)\n\t}\n\n\t// If we have no state, we have to create an empty state","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/consul/backend_state.go#L90-L126","documentation":"Thrown by the Consul backend's StateMgr during initialization of a non-default workspace. The backend acquires a Consul lock so it can atomically write an empty sentinel state (so States() can later discover the workspace). If stateMgr.Lock returns an error, it is wrapped here. The underlying cause is typically that another client holds the lock, the Consul agent is unreachable, or the ACL token cannot create a session or write the lock key.","triggerScenarios":"backend.StateMgr(name) with name != backend.DefaultStateName -> stateMgr.Lock(lockInfo) returns a non-nil error. The lock fails inside RemoteClient.lock() when createSession() or consulLock.Lock(...) errors out (e.g. another session holds the key, LockWaitTime elapsed after LockTryOnce, ACL denied, network error).","commonSituations":"A previous Terraform run crashed without releasing its Consul session; a teammate is running terraform apply against the same workspace; the Consul ACL token used by the backend lacks session:write or kv:write on the state prefix; the Consul agent is down or partitioned; the session TTL expired mid-run.","solutions":["Read the wrapped error: if it names a lock holder / lock ID, run `terraform force-unlock <LOCK_ID>` (the ID is the Consul session ID).","Check Consul health and connectivity from the Terraform host: `consul members`, `consul kv get -recurse <state-path>`.","Verify the backend's ACL token grants session:write and kv:write on the state prefix; recreate the token if revoked.","Coordinate with the team so only one run targets the workspace at a time; re-run terraform init/apply once the lock is cleared."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-check whether a Consul lock is already held for the state path\n// before attempting StateMgr init.\nfunc consulLockHeld(client *consulapi.Client, statePath string) (bool, error) {\n    pair, _, err := client.KV().Get(strings.TrimRight(statePath, \"/\")+\".lock\", nil)\n    if err != nil {\n        return false, err\n    }\n    return pair != nil, nil\n}\n\n// if held, surface a clear message instead of letting Lock() fail opaquely.","typeGuard":null,"tryCatchPattern":"// Treat statemgr.LockError specially: surface the lock ID so the user can force-unlock.\nsm, diags := backend.StateMgr(name)\nif diags.HasErrors() {\n    var le *statemgr.LockError\n    for _, d := range diags {\n        if errors.As(d.Err(), &le) && le.Info != nil {\n            return fmt.Errorf(\"state %q is locked (id=%s); run terraform force-unlock %s\", name, le.Info.ID, le.Info.ID)\n        }\n    }\n    return diags.Err()\n}","preventionTips":["Run only one Terraform operation per workspace at a time; use CI concurrency limits.","Ensure the Consul ACL token has session:write and kv:write on the state prefix.","Use the lock = true default; do not disable locking to dodge contention.","Wire terraform force-unlock into your runbook so crashed runs are cleaned up promptly."],"tags":["consul","backend","state-locking","terraform"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}