hashicorp/terraform · error

failed to lock s3 state: %s

Error message

failed to lock s3 state: %s

What it means

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.

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 c9def3e214)

Solutions

  1. Run `terraform force-unlock <lock-id>` using the ID shown in the lock error info.
  2. Inspect and, if stale, manually delete the <key>.tflock object in S3 and/or the LockID row in DynamoDB.
  3. Confirm the dynamodb_table config points to an existing table and the role can read/write it.
  4. Coordinate with concurrent runs and retry once free.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check: is the state already locked before init?
// _, err := s3Client.HeadObject(ctx, &s3.HeadObjectInput{Bucket:&bucket, Key:aws.String(lockFilePath)})
// if err == nil { /* locked */ }

Try / catch

// client.Lock returns *statemgr.LockError with Info about the holder
// _, err := client.Lock(info)
// var le *statemgr.LockError
// if errors.As(err, &le) {
//   fmt.Printf("locked by %s; terraform force-unlock %s\n", le.Info.Who, le.Info.ID)
// }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/e1253c22fd884403. Report an issue: GitHub.