hashicorp/terraform · error

error deleting workspace

Error message

error deleting workspace %s: %w

What it means

Thrown by Local.DeleteWorkspace in the local backend when os.RemoveAll fails on the on-disk workspace state directory (filepath.Join(stateWorkspaceDir(), name)). The underlying OS error is wrapped with %w so callers can inspect it. DeleteWorkspace has already validated that name is non-empty and not the default workspace before reaching this point.

Solutions

  1. Check the wrapped OS error first: `errors.Is(err, os.ErrPermission)` etc. to know whether it is permissions, missing path, or something else.
  2. Fix filesystem permissions on the state workspace directory so the terraform process can write and remove files under it.
  3. Close any process holding the file open (editors, antivirus, sync clients) and retry.
  4. If the directory was already removed externally, the workspace is effectively gone; clean up any cached in-memory entry and re-list workspaces.

Example fix

// before: assumes RemoveAll always succeeds
_ = os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name))
// after: surface + classify the error
if err := os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name)); err != nil {
    return diags.Append(fmt.Errorf("error deleting workspace %s: %w", name, err))
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the workspace dir is removable before calling DeleteWorkspace.
func canRemoveWorkspaceDir(base, name string) error {
    p := filepath.Join(base, name)
    info, err := os.Stat(p)
    if err != nil && !os.IsNotExist(err) { return err }
    if err == nil && !info.IsDir() { return fmt.Errorf("%s is not a directory", p) }
    // attempt a no-op write to verify writability of the parent
    return os.WriteFile(filepath.Join(base, ".rmprobe"), []byte{}, 0o644)
}

Type guard

null

Try / catch

if diags := b.DeleteWorkspace(name, force); diags.HasErrors() {
    err := diags.Err()
    if errors.Is(err, os.ErrPermission) {
        // guide the user to fix perms
    }
    return err
}

Prevention

When it happens

Trigger: Calling terraform workspace delete <name> (or backend.DeleteWorkspace programmatically) against the local backend, where RemoveAll returns a non-nil error: permission denied, not a directory, ENOENT after a race, read-only filesystem, or a file held open by another process (common on Windows).

Common situations: State directory chmod'd to read-only, antivirus or backup software locking the file on Windows, the workspace dir was already removed out-of-band, NFS/SMB share permission mismatch, or running as a different user than the one that created the state dir.

Related errors


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

Appendix: source

Thrown at internal/backend/local/backend.go:254

	var diags tfdiags.Diagnostics

	// If we have a backend handling state, defer to that.
	if b.Backend != nil {
		return b.Backend.DeleteWorkspace(name, force)
	}

	if name == "" {
		return diags.Append(errors.New("empty state name"))
	}

	if name == backend.DefaultStateName {
		return diags.Append(errors.New("cannot delete default state"))
	}

	delete(b.states, name)
	err := os.RemoveAll(filepath.Join(b.stateWorkspaceDir(), name))
	if err != nil {
		return diags.Append(fmt.Errorf("error deleting workspace %s: %w", name, err))
	}

	return diags
}

func (b *Local) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics

	// If we have a backend handling state, delegate to that.
	if b.Backend != nil {
		return b.Backend.StateMgr(name)
	}

	if s, ok := b.states[name]; ok {
		return s, diags
	}

	if err := b.createState(name); err != nil {

View on GitHub (pinned to d32a084675)