plandex-ai/plandex · error
error deleting draft plan dir: %v
Error message
error deleting draft plan dir: %v
What it means
After collecting ids of deleted draft plans, DeleteDraftPlans waits on errCh for each per-plan directory deletion. This error wraps the first error returned by DeletePlanDir for any plan, meaning the on-disk plan directory could not be removed.
Source
Thrown at app/server/db/plan_helpers.go:389
errCh := make(chan error, len(ids))
for _, planId := range ids {
go func(planId string) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in DeleteDraftPlans: %v\n%s", r, debug.Stack())
errCh <- fmt.Errorf("panic in DeleteDraftPlans: %v\n%s", r, debug.Stack())
runtime.Goexit() // don't allow outer function to continue and double-send to channel
}
}()
errCh <- DeletePlanDir(orgId, planId)
}(planId)
}
for i := 0; i < len(ids); i++ {
err := <-errCh
if err != nil {
return fmt.Errorf("error deleting draft plan dir: %v", err)
}
}
if len(ids) > 0 {
log.Println("Deleted", len(ids), "draft plans")
}
return nil
}
func DeleteOwnerPlans(orgId, projectId, userId string) error {
res, err := Conn.Query("DELETE FROM plans WHERE project_id = $1 AND owner_id = $2 RETURNING id;", projectId, userId)
if err != nil {
return fmt.Errorf("error deleting plans: %v", err)
}
defer res.Close()
View on GitHub (pinned to e2d772072e)
Solutions
- Check the wrapped OS error from DeletePlanDir (EACCES/ENOENT/EBUSY)
- Treat already-deleted directories (ENOENT) as success to tolerate races
- Fix filesystem permissions/ownership on the plan storage root
- Serialize or lock plan deletion per planId to avoid concurrent delete races
Example fix
// before
return fmt.Errorf("error deleting draft plan dir: %v", err)
// after
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("error deleting draft plan dir: %w", err) Defensive patterns
Strategy: fallback
Validate before calling
if _, err := os.Stat(planDir); err == nil {
// dir exists and can be deleted
} Try / catch
err := DeleteDraftPlans(orgId, projectId, userId)
if err != nil {
var pe *fs.PathError
if errors.As(err, &pe) && errors.Is(pe.Err, syscall.ENOENT) {
// already deleted; safe to ignore
} else {
return err
}
} Prevention
- Treat ENOENT as idempotent success in directory cleanup
- Lock per-plan deletion to avoid concurrent delete races
- Check permissions/ownership on the plan storage root
- Monitor for EBUSY/ESTALE errors on network filesystems
When it happens
Trigger: DeletePlanDir(orgId, planId) returns an error: permission denied on plan directory, directory already removed by a concurrent request (ENOENT handling differs), or filesystem I/O failure.
Common situations: Multiple tabs/requests creating and deleting drafts concurrently causing races; read-only or full disk; wrong ownership of plan data directories after a container user change; NFS stale-handle errors.
Related errors
- error deleting pending results: %v
- failed to read the file %s: %v
- error getting plan current branch: %v
- error reading convo files: %v
- panic in DeleteOwnerPlans: %v %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/6c9076020016c628.
Report an issue: GitHub.