{"record":{"id":"c70d6e36937db009","repo":"hashicorp/terraform","slug":"error-unlocking-s3-state-lock-id-s-error-s","errorCode":null,"errorMessage":"Error unlocking S3 state. Lock ID: %s\n\nError: %s\n\nYou may have to force-unlock this state in order to use it again.","messagePattern":"Error unlocking S3 state\\. Lock ID: (.+?)\n\nError: (.+?)\n\nYou may have to force-unlock this state in order to use it again\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/backend_state.go","lineNumber":223,"sourceCode":"\t\t\texists = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// We need to create the object so it's listed by States.\n\tif !exists {\n\t\t// take a lock on this state while we write it\n\t\tlockInfo := statemgr.NewLockInfo()\n\t\tlockInfo.Operation = \"init\"\n\t\tlockId, err := client.Lock(lockInfo)\n\t\tif err != nil {\n\t\t\treturn nil, diags.Append(fmt.Errorf(\"failed to lock s3 state: %s\", err))\n\t\t}\n\n\t\t// Local helper function so we can call it multiple places\n\t\tlockUnlock := func(parent error) error {\n\t\t\tif err := stateMgr.Unlock(lockId); err != nil {\n\t\t\t\treturn fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)\n\t\t\t}\n\t\t\treturn parent\n\t\t}\n\n\t\t// Grab the value\n\t\t// This is to ensure that no one beat us to writing a state between\n\t\t// the `exists` check and taking the lock.\n\t\tif err := stateMgr.RefreshState(); err != nil {\n\t\t\terr = lockUnlock(err)\n\t\t\treturn nil, diags.Append(err)\n\t\t}\n\n\t\t// If we have no state, we have to create an empty state\n\t\tif v := stateMgr.State(); v == nil {\n\t\t\tif err := stateMgr.WriteState(states.NewState()); err != nil {\n\t\t\t\terr = lockUnlock(err)\n\t\t\t\treturn nil, diags.Append(err)\n\t\t\t}","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/backend_state.go#L205-L241","documentation":"Wrapped by the lockUnlock closure inside Backend.StateMgr (S3) when stateMgr.Unlock(lockId) fails during cleanup of an init that errored after acquiring the lock. The errStateUnlock template tells the operator they may need to force-unlock. The original error is chained as parent; the unlock failure is reported alongside it.","triggerScenarios":"stateMgr.Unlock(lockId) returns err != nil in lockUnlock, called from the RefreshState/WriteState/PersistState error branches in the init path. Causes: DynamoDB unreachable, lock row deleted out-of-band, IAM permission revoked mid-run, or the lock ID no longer matches the row (someone force-unlocked already).","commonSituations":"DynamoDB table deleted during apply; throttling on the lock table; another operator force-unlocked the same state mid-run; AWS region impairment.","solutions":["Run `tofu force-unlock <lock-id>` to clear the DynamoDB lock row if still present.","Confirm the DynamoDB lock table exists and is reachable.","Re-run `tofu init` / `tofu apply` after clearing the lock.","Audit IAM permissions for dynamodb:DeleteItem on the lock table."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// preflight: ensure the lock row still exists before attempting unlock\n_, err := dbClient.GetItem(ctx, &dynamodb.GetItemInput{TableName: aws.String(lockTable), Key: map[string]types.AttributeValue{\"LockID\": &types.AttributeValueMemberS{Value: lockID}}})\nif err != nil { return fmt.Errorf(\"lock table unreadable; unlock may fail: %w\", err) }","typeGuard":null,"tryCatchPattern":"if err := stateMgr.Unlock(lockId); err != nil {\n    log.Printf(\"WARN unlock failed for %s; operator may need `tofu force-unlock %s`: %v\", lockId, lockId, err)\n    return parent\n}","preventionTips":["Always log the lock ID alongside unlock failures so operators can force-unlock.","Audit IAM for dynamodb:DeleteItem on the lock table.","Never delete the DynamoDB lock table while applies are in flight."],"tags":["s3","aws","state-lock","dynamodb","unlock","cleanup"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}