{"record":{"id":"e1253c22fd884403","repo":"hashicorp/terraform","slug":"failed-to-lock-s3-state-s","errorCode":null,"errorMessage":"failed to lock s3 state: %s","messagePattern":"failed to lock s3 state: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/remote-state/s3/backend_state.go","lineNumber":217,"sourceCode":"\t\treturn nil, diags\n\t}\n\n\texists := false\n\tfor _, s := range existing {\n\t\tif s == name {\n\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","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/remote-state/s3/backend_state.go#L199-L235","documentation":"Thrown during Backend.StateMgr init in the S3 backend when a workspace does not yet exist and client.Lock(lockInfo) returns an error while taking the init lock. The lock guards the creation of the empty sentinel state object so Workspaces() lists it. %s wraps the underlying lock error (usually a DynamoDB-backed *statemgr.LockError).","triggerScenarios":"client.Lock(lockInfo) fails in the !exists branch. Causes: DynamoDB lock table missing or misconfigured, the state key is already locked by another run, IAM permissions lacking on the lock table, or DynamoDB throttling.","commonSituations":"Concurrent first-applies for a new workspace; `dynamodb_table` pointing at a non-existent table; principal lacks dynamodb:GetItem/PutItem/DeleteItem; region mismatch between S3 and DynamoDB.","solutions":["Confirm `dynamodb_table` exists in the same region: `aws dynamodb describe-table --table-name <name>`.","Inspect the wrapped error for 'workspace is already locked' - wait or `tofu force-unlock`.","Grant the principal dynamodb:GetItem/PutItem/DeleteItem on the lock table ARN.","Re-run `tofu init` after fixing config."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// preflight: confirm the DynamoDB lock table exists and is writable\n_, err := dbClient.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: aws.String(lockTable)})\nif err != nil { return fmt.Errorf(\"lock table %s missing or unreachable: %w\", lockTable, err) }","typeGuard":"func isLockNotConfiguredErr(err error) bool {\n    var le *statemgr.LockError\n    return errors.As(err, &le) && strings.Contains(le.Err.Error(), \"does not exist\")\n}","tryCatchPattern":"lockId, err := client.Lock(lockInfo)\nif err != nil {\n    var le *statemgr.LockError\n    if errors.As(err, &le) && le.Info != nil { return fmt.Errorf(\"state busy; run `tofu force-unlock %s`\", le.Info.ID) }\n    return err\n}","preventionTips":["Create the DynamoDB lock table before first init: `aws dynamodb create-table ...`.","Keep the lock table in the same region as the state bucket.","Grant dynamodb:GetItem/PutItem/DeleteItem on the lock table ARN."],"tags":["s3","aws","state-lock","dynamodb","init"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}