hashicorp/terraform · error
failed to lock s3 state
Error message
failed to lock s3 state: %s
What it means
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).
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.
Defensive patterns
Strategy: validation
Validate before calling
// preflight: confirm the DynamoDB lock table exists and is writable
_, err := dbClient.DescribeTable(ctx, &dynamodb.DescribeTableInput{TableName: aws.String(lockTable)})
if err != nil { return fmt.Errorf("lock table %s missing or unreachable: %w", lockTable, err) } Type guard
func isLockNotConfiguredErr(err error) bool {
var le *statemgr.LockError
return errors.As(err, &le) && strings.Contains(le.Err.Error(), "does not exist")
} Try / catch
lockId, err := client.Lock(lockInfo)
if err != nil {
var le *statemgr.LockError
if errors.As(err, &le) && le.Info != nil { return fmt.Errorf("state busy; run `tofu force-unlock %s`", le.Info.ID) }
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Error unlocking S3 state. Lock ID
- can't delete default state
- Error locking state
- failed to clean up file lock after DynamoDB lock error
- failed to lock state in Postgres
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/e1253c22fd884403.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/s3/backend_state.go:217
return nil, diags
}
exists := false
for _, s := range existing {
if s == name {
exists = true
break
}
}
// We need to create the object so it's listed by States.
if !exists {
// take a lock on this state while we write it
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := client.Lock(lockInfo)
if err != nil {
return nil, diags.Append(fmt.Errorf("failed to lock s3 state: %s", err))
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}
return parent
}
// Grab the value
// This is to ensure that no one beat us to writing a state between
// the `exists` check and taking the lock.
if err := stateMgr.RefreshState(); err != nil {
err = lockUnlock(err)
return nil, diags.Append(err)
}
View on GitHub (pinned to d32a084675)