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
- Check the wrapped OS error first: `errors.Is(err, os.ErrPermission)` etc. to know whether it is permissions, missing path, or something else.
- Fix filesystem permissions on the state workspace directory so the terraform process can write and remove files under it.
- Close any process holding the file open (editors, antivirus, sync clients) and retry.
- 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
- Run terraform as a user that owns the state workspace directory.
- On Windows, close editors/AV/sync clients that may hold state files open before deleting a workspace.
- Do not chmod the state dir read-only; the local backend needs write access for create and delete.
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
- can not read private key from
- can't read
- could not write lock info for
- error loading state
- Error loading statefile
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)