hashicorp/terraform · error

Failed to delete state file %v: %v

Error message

Failed to delete state file %v: %v

What it means

remoteClient.Delete() calls stateFile().Delete(ctx) to remove the .tfstate object; any error (object not present is still an error here, unlike Get) is wrapped with the gs:// URL. Triggered by DeleteWorkspace after the name passes validation, and by explicit state-file deletion.

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

Solutions

  1. If %v is 'notFound', the workspace is already gone — treat as success in automation (no-op).
  2. Grant SA 'roles/storage.objectAdmin' (includes delete) or a custom role with storage.objects.delete.
  3. Remove bucket retention policy / object holds blocking the delete, or wait for retention to elapse.
  4. Retry; transient 5xx during delete is possible.

Example fix

// before: SA lacks delete
roles/storage.objectCreator only

// after
gsutil iam ch serviceAccount:tf@proj.iam.gserviceaccount.com:roles/storage.objectAdmin gs://bucket
terraform workspace delete <ws>
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check object existence to make delete idempotent
ctx := context.Background()
_, err := client.Bucket(bucket).Object(statePath).Attrs(ctx)
if err == storage.ErrObjectNotExist { return nil }

Type guard

import "strings"
func isAlreadyGone(err error) bool { return strings.Contains(err.Error(), "notFound") || err == storage.ErrObjectNotExist }

Try / catch

if diags := client.Delete(); diags.HasErrors() {
    if isAlreadyGone(diags.Err()) { return nil }  // idempotent
    return diags
}

Prevention

When it happens

Trigger: 'terraform workspace delete <ws>' removing its state object; the object was already deleted by another process (returns notFound); SA lacks storage.objects.delete; bucket has a retention policy holding the object.

Common situations: Idempotent workspace deletion racing with another teardown; SA has objectAdmin for create/get but not delete; retention policy / object holds blocking deletion.

Related errors


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