{"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/c9def3e214014c1188faabfc4a5bde5095139765/internal/backend/remote-state/s3/backend_state.go#L199-L235","documentation":"Thrown by Backend.StateMgr (s3/backend_state.go:217) during first-time workspace init when client.Lock() fails. The underlying error is a *statemgr.LockError from the S3 native (.tflock) lock and/or the DynamoDB lock, wrapped here with %s.","triggerScenarios":"Another run already holds the lock (S3 .tflock file exists, or DynamoDB LockID row present); DynamoDB ConditionalCheckFailedException; DynamoDB table missing/inaccessible; S3 PutObject for the lock file denied.","commonSituations":"Concurrent apply/plan on a brand-new workspace; a previous run crashed leaving a stale .tflock or DynamoDB row; misconfigured dynamodb_table name; IAM lacking the lock write permission.","solutions":["Run `terraform force-unlock <lock-id>` using the ID shown in the lock error info.","Inspect and, if stale, manually delete the <key>.tflock object in S3 and/or the LockID row in DynamoDB.","Confirm the dynamodb_table config points to an existing table and the role can read/write it.","Coordinate with concurrent runs and retry once free."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-check: is the state already locked before init?\n// _, err := s3Client.HeadObject(ctx, &s3.HeadObjectInput{Bucket:&bucket, Key:aws.String(lockFilePath)})\n// if err == nil { /* locked */ }","typeGuard":null,"tryCatchPattern":"// client.Lock returns *statemgr.LockError with Info about the holder\n// _, err := client.Lock(info)\n// var le *statemgr.LockError\n// if errors.As(err, &le) {\n//   fmt.Printf(\"locked by %s; terraform force-unlock %s\\n\", le.Info.Who, le.Info.ID)\n// }","preventionTips":["Always configure dynamodb_table or use_lock_file so locking is enforced.","CI should acquire the workspace lock via a single coordinator.","Clean up stale .tflock objects and DynamoDB rows after crashed runs."],"tags":["s3","locking","dynamodb","remote-state"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}