{"record":{"id":"5f7147e6d3f8c938","repo":"hashicorp/terraform","slug":"error-locking-state-s","errorCode":null,"errorMessage":"Error locking state: %s","messagePattern":"Error locking state: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/command/meta_backend.go","lineNumber":1778,"sourceCode":"\t\t\tfor _, localState := range localStates {\n\t\t\t\t// We always delete the local state, unless that was our new state too.\n\t\t\t\tif err := localState.WriteState(nil); err != nil {\n\t\t\t\t\tdiags = diags.Append(&errBackendMigrateLocalDelete{err})\n\t\t\t\t\treturn nil, diags\n\t\t\t\t}\n\t\t\t\tif err := localState.PersistState(nil); err != nil {\n\t\t\t\t\tdiags = diags.Append(&errBackendMigrateLocalDelete{err})\n\t\t\t\t\treturn nil, diags\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif m.stateLock {\n\t\tview := views.NewStateLocker(vt, m.View)\n\t\tstateLocker := clistate.NewLocker(m.stateLockTimeout, view)\n\t\tif err := stateLocker.Lock(sMgr, \"backend from plan\"); err != nil {\n\t\t\tdiags = diags.Append(fmt.Errorf(\"Error locking state: %s\", err))\n\t\t\treturn nil, diags\n\t\t}\n\t\tdefer stateLocker.Unlock()\n\t}\n\n\t// Store the metadata in our saved state location\n\ts := sMgr.State()\n\tif s == nil {\n\t\ts = workdir.NewBackendStateFile()\n\t}\n\ts.Backend = &workdir.BackendConfigState{\n\t\tType: c.Type,\n\t\tHash: uint64(cHash),\n\t}\n\terr := s.Backend.SetConfig(configVal, b.ConfigSchema())\n\tif err != nil {\n\t\tdiags = diags.Append(fmt.Errorf(\"Can't serialize backend configuration as JSON: %s\", err))\n\t\treturn nil, diags","sourceCodeStart":1760,"sourceCodeEnd":1796,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/command/meta_backend.go#L1760-L1796","documentation":"During a backend initialization/migration, after optional local-state deletion, Terraform tries to acquire the state lock via clistate.Locker.Lock(sMgr, \"backend from plan\"). If acquisition fails the raw error is wrapped. Competing processes, stale locks, or an unresponsive remote backend are typical root causes.","triggerScenarios":"stateLocker.Lock returns an error on sMgr. Triggers: another `tofu`/`terraform` process already holds the lock, a previous crashed run left a stale lock in the backend/lock file, the backend's lock API is unreachable, or the local state file is locked at the OS level.","commonSituations":"Concurrent CI jobs on the same state; a killed process that did not release its lock; remote backend (e.g. S3+DynamoDB) where the lock table is missing or inaccessible; cross-region latency causing lock-acquire timeout.","solutions":["Confirm no other run is active (`ps`, CI queue) before forcing anything.","If truly stale, force-release with `tofu force-unlock <lock-id>` (id is in the inner error) then retry.","Verify the backend's locking prerequisite exists (e.g. DynamoDB table for s3 backend) and credentials/permissions are correct.","Use `-lock-timeout=<duration>` to wait for a transient lock instead of failing immediately.","For local state, ensure no editor or other tool holds the .terraform.tfstate.lock file open."],"exampleFix":"// before\ntofu init  # another process holds the lock\n# -> Error locking state: ...\n\n// after\ntofu force-unlock <lock-id-from-error>\ntofu init -lock-timeout=60s","handlingStrategy":"retry","validationCode":"// Before init, probe that the state lock is acquirable / no stale lock exists.\nfunc probeStateLockAcquirable(sMgr *clistate.LocalState, timeout time.Duration) error {\n    locker := clistate.NewLocker(timeout, views.NewStateLocker(views.StateLockerNoOp, nil))\n    if err := locker.Lock(sMgr, \"probe\"); err != nil {\n        return fmt.Errorf(\"state lock not acquirable: %w\", err)\n    }\n    defer locker.Unlock()\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := stateLocker.Lock(sMgr, \"backend from plan\"); err != nil {\n    if isLockHeldErr(err) {\n        diags = diags.Append(fmt.Errorf(\"Error locking state (held by another run; use 'tofu force-unlock <id>' if stale): %s\", err))\n    } else {\n        diags = diags.Append(fmt.Errorf(\"Error locking state: %s\", err))\n    }\n    return nil, diags\n}","preventionTips":["Serialize CI jobs that touch the same state (mutex/queue).","Always release locks: let commands finish; avoid SIGKILL mid-run.","Confirm backend lock prerequisites (e.g. DynamoDB table) exist before init.","Use `-lock-timeout=` to absorb brief contention instead of failing fast."],"tags":["backend","state-lock","init","migration","concurrency"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}