{"record":{"id":"819034e32b9fd216","repo":"hashicorp/terraform","slug":"error-unlocking-consul-state-lock-id-s-error","errorCode":null,"errorMessage":"Error unlocking Consul state. Lock ID: %s\n\nError: %s\n\nYou may have to force-unlock this state in order to use it again.\nThe Consul backend acquires a lock during initialization to ensure\nthe minimum required key/values are prepared.","messagePattern":"Error unlocking Consul state\\. Lock ID: (.+?)\n\nError: (.+?)\n\nYou may have to force-unlock this state in order to use it again\\.\nThe Consul backend acquires a lock during initialization to ensure\nthe minimum required key/values are prepared\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/consul/backend_state.go","lineNumber":114,"sourceCode":"\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\n\tif v := stateMgr.State(); v == nil {\n\t\tif err := stateMgr.WriteState(states.NewState()); err != nil {\n\t\t\terr = lockUnlock(err)\n\t\t\treturn nil, diags.Append(err)\n\t\t}\n\t\tif err := stateMgr.PersistState(nil); err != nil {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/consul/backend_state.go#L96-L132","documentation":"After acquiring the init lock for a non-default workspace, the backend runs RefreshState/WriteState/PersistState inside a lockUnlock helper. If any of those steps fails AND the subsequent stateMgr.Unlock(lockId) also fails, this formatted errStateUnlock is raised and the user is told to force-unlock. It indicates Terraform could not cleanly release the lock it just took.","triggerScenarios":"In backend_state.go StateMgr: RefreshState/WriteState/PersistState returns an error; lockUnlock(parent) calls stateMgr.Unlock(lockId) which returns non-nil; the helper returns fmt.Errorf(errStateUnlock, lockId, err).","commonSituations":"Consul session TTL (15s default) expired while Terraform was still processing; Consul was restarted or lost quorum mid-operation; network dropped between the Terraform host and Consul; the ACL token was revoked during the run.","solutions":["Capture the Lock ID printed in the message and run `terraform force-unlock <LOCK_ID>` (Consul session ID).","Inspect `consul session list` to see whether the session still exists; if gone, the lock will auto-release but force-unlock clears the local view faster.","Verify Consul connectivity (`consul info`, `consul members`) and ACL token validity before retrying.","Re-run the Terraform command once the lock is released."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// After a failed init that may have left a lock, attempt force-unlock once.\nsm, diags := backend.StateMgr(name)\nif diags.HasErrors() {\n    msg := diags.Err().Error()\n    if strings.Contains(msg, \"force-unlock\") {\n        if id := extractLockID(msg); id != \"\" {\n            log.Printf(\"init failed with dangling lock %s; attempting force-unlock\", id)\n            if _, uerr := sm.Unlock(id); uerr != nil {\n                return fmt.Errorf(\"manual recovery required: %w\", uerr)\n            }\n        }\n    }\n    return diags.Err()\n}","preventionTips":["Keep Consul well-connected from the Terraform host; prefer a local agent.","Tune session TTLs only with care; shorter TTLs make this more likely.","Do not revoke the backend ACL token mid-run.","Have a force-unlock runbook ready; capture lock IDs from CI logs."],"tags":["consul","backend","state-locking","recovery"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}