hashicorp/terraform · error

failed to delete the lock file: %w

Error message

failed to delete the lock file: %w

What it means

Thrown by unlockWithFile after the lock ID is verified and the code calls s3Client.DeleteObject on the .tflock key but AWS returns an error. The lock was successfully validated but could not be removed, so the lock effectively remains.

Source

Thrown at internal/backend/remote-state/s3/client.go:553

	lockInfo := &statemgr.LockInfo{}
	if err := json.Unmarshal(data, lockInfo); err != nil {
		return fmt.Errorf("failed to unmarshal JSON data into LockInfo struct: %w", err)
	}
	lockErr.Info = lockInfo

	// Verify that the provided lock ID matches the lock ID of the retrieved lock file.
	if lockInfo.ID != id {
		return fmt.Errorf("lock ID '%s' does not match the existing lock ID '%s'", id, lockInfo.ID)
	}

	// Delete the lock file to release the lock.
	_, err = c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: aws.String(c.bucketName),
		Key:    aws.String(c.lockFilePath),
	})

	if err != nil {
		return fmt.Errorf("failed to delete the lock file: %w", err)
	}

	log.Debug(fmt.Sprintf("Deleted lock file: '%q'", c.lockFilePath))

	return nil
}

func (c *RemoteClient) unlockWithDynamoDB(ctx context.Context, id string, lockErr *statemgr.LockError) error {
	// TODO: store the path and lock ID in separate fields, and have proper
	// projection expression only delete the lock if both match, rather than
	// checking the ID from the info field first.
	lockInfo, err := c.getLockInfoWithDynamoDB(ctx)
	if err != nil {
		return fmt.Errorf("failed to retrieve lock info for lock ID %q: %s", id, err)
	}
	lockErr.Info = lockInfo

	if lockInfo.ID != id {

View on GitHub (pinned to c9def3e214)

Solutions

  1. Confirm s3:DeleteObject permission on arn:aws:s3:::<bucket>/<lockFilePath>.
  2. Check the bucket is not in S3 Object Lock / WORM mode that blocks deletes.
  3. Review bucket policies and SCPs for explicit Deny on DeleteObject.
  4. As a last resort, use lifecycle rules or versioning suspend to release the lock, or delete via a privileged account.

Example fix

// before: only GetObject granted
// after
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:DeleteObject"],
  "Resource": "arn:aws:s3:::my-state-bucket/*"
}
Defensive patterns

Strategy: validation

Validate before calling

// IAM-side check: ensure DeleteObject is granted (simulate or use IAM evaluator)
// Runtime pre-flight: try deleting a no-op test object under the same prefix
func canDeleteInPrefix(ctx context.Context, s3c *s3.Client, bucket string) error {
  probe := "tflock-probe-" + uuid.NewString()
  if _, err := s3c.PutObject(ctx, &s3.PutObjectInput{Bucket: &bucket, Key: &probe, Body: strings.NewReader("")}); err != nil { return err }
  _, err := s3c.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: &bucket, Key: &probe})
  return err
}

Try / catch

if err := client.Unlock(id); err != nil {
    var ae smithy.APIError
    if errors.As(err, &ae) && (ae.ErrorCode()=="AccessDenied" || ae.ErrorCode()=="AccessDeniedException") {
        // surface IAM remediation guidance
    }
}

Prevention

When it happens

Trigger: Unlock -> unlockWithFile where c.s3Client.DeleteObject fails on the verified lock file: AccessDenied, bucket policy Deny, object in Glacier deep archive, KMS denial, or a transient AWS error.

Common situations: IAM principal has GetObject but not DeleteObject; a bucket policy explicitly denies deletes; object-locked bucket (WORM) prevents deletion; region mismatch; or a service control policy blocking deletes.

Related errors


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