hashicorp/terraform · error

Failed to delete state file

Error message

Failed to delete state file %v: %v

What it means

Thrown by the remote client Delete when c.stateFile().Delete(ctx) fails. Delete is used by DeleteWorkspace to remove a workspace's state object.

Solutions

  1. Grant roles/storage.objectAdmin which includes objects.delete.
  2. Confirm the workspace still has a state object; idempotently tolerate 'not found'.
  3. Check bucket retention policy if deletes are being rejected.
  4. Retry transient errors.

Example fix

// before — no delete permission
// after
gsutil iam ch serviceAccount:terraform@proj.iam.gserviceaccount.com:roles/storage.objectAdmin gs://tf-state
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: confirm objects.delete permission.
// Minimal test: gsutil rm gs://bucket/.delprobe

Try / catch

// Make delete idempotent for 'not found'.
if err := f.Delete(ctx); err != nil && !errors.Is(err, storage.ErrObjectNotExist) {
    return err
}

Prevention

When it happens

Trigger: stateFile().Delete returns an error — missing storage.objects.delete permission, object does not exist (though typically that is tolerated by the API), precondition failure, or transport error.

Common situations: Service account has objectAdmin minus delete; attempting to delete a workspace whose state was already removed; object held by a retention policy; transient failures.

Related errors


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

Appendix: source

Thrown at internal/backend/remote-state/gcs/client.go:88

		}
		if _, err := stateFileWriter.Write(data); err != nil {
			return err
		}
		return stateFileWriter.Close()
	}()
	if err != nil {
		return diags.Append(fmt.Errorf("Failed to upload state to %v: %v", c.stateFileURL(), err))
	}

	return diags
}

func (c *remoteClient) Delete() tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics

	ctx := context.TODO()
	if err := c.stateFile().Delete(ctx); err != nil {
		return diags.Append(fmt.Errorf("Failed to delete state file %v: %v", c.stateFileURL(), err))
	}

	return diags
}

// Lock writes to a lock file, ensuring file creation. Returns the generation
// number, which must be passed to Unlock().
func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
	ctx := context.TODO()

	// update the path we're using
	// we can't set the ID until the info is written
	info.Path = c.lockFileURL()

	infoJson, err := json.Marshal(info)
	if err != nil {
		return "", err
	}

View on GitHub (pinned to d32a084675)