plandex-ai/plandex · error
Error deleting plan dir:
Error message
Error deleting plan dir:
What it means
DeletePlanHandler (plans_crud.go:258) fails after the DB row was successfully deleted but db.DeletePlanDir(orgId, planId) could not remove the plan's directory on disk. This error leaves the plan deleted in the database while its files remain, i.e. a partially completed delete. Typical causes are filesystem permission problems or a missing/locked directory.
Source
Thrown at app/server/handlers/plans_crud.go:258
rowsAffected, err := res.RowsAffected()
if err != nil {
log.Printf("Error getting rows affected: %v\n", err)
http.Error(w, "Error getting rows affected: "+err.Error(), http.StatusInternalServerError)
return
}
if rowsAffected == 0 {
log.Println("Plan not found")
http.Error(w, "Not found", http.StatusNotFound)
return
}
err = db.DeletePlanDir(auth.OrgId, planId)
if err != nil {
log.Printf("Error deleting plan dir: %v\n", err)
http.Error(w, "Error deleting plan dir: "+err.Error(), http.StatusInternalServerError)
return
}
log.Println("Successfully deleted plan", planId)
}
func DeleteAllPlansHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Received request for DeleteAllPlansHandler")
auth := Authenticate(w, r, true)
if auth == nil {
return
}
vars := mux.Vars(r)
projectId := vars["projectId"]
log.Println("projectId: ", projectId)View on GitHub (pinned to e2d772072e)
Solutions
- Inspect the logged driver error to see if it's ENOENT (dir already gone — safe to treat as success), EACCES/EPERM (fix ownership: chown -R the storage volume to the server user), or EBUSY (stop processes holding the dir).
- Make DeletePlanDir idempotent: ignore os.IsNotExist so re-deleting an already-cleaned dir succeeds.
- Verify storage volume mount permissions match the server process UID/GID.
- Add a cleanup/reconciliation job that removes plan dirs with no matching DB row.
Example fix
// before
if err := os.RemoveAll(dir); err != nil {
return err
}
// after
if err := os.RemoveAll(dir); err != nil && !os.IsNotExist(err) {
return err
} Defensive patterns
Strategy: validation
Validate before calling
if _, err := os.Stat(planDir); os.IsNotExist(err) {
return nil // nothing to clean up
} Type guard
func isPermissionErr(err error) bool {
var pathErr *fs.PathError
return errors.As(err, &pathErr) && errors.Is(pathErr.Err, os.ErrPermission)
} Try / catch
if err := db.DeletePlanDir(orgId, planId); err != nil {
if os.IsNotExist(err) {
log.Println("plan dir already removed — continuing")
} else {
log.Printf("Error deleting plan dir: %v", err) // row already deleted; alert for cleanup job
}
} Prevention
- Run the server with a UID that owns the storage volume; align chown/mount permissions.
- Make DeletePlanDir idempotent by ignoring os.ErrNotExist.
- Run a reconciliation job deleting orphaned plan directories.
- Stop running executions before deleting their plan directories.
When it happens
Trigger: DELETE row succeeded, then DeletePlanDir errors because the plan directory does not exist, is owned by another user/UID (container permission drift), or a file inside is held open/locked by a running process.
Common situations: Server container running as non-root while plan dirs were created as root; volumes mounted with wrong permissions; plan dir already manually cleaned up; a running execution still holding files open.
Related errors
- error walking directory: %s
- failed to check if %s exists: %s
- failed to read %s: %s
- failed to write %s: %s
- failed to remove %s: %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/fbf4568464512dde.
Report an issue: GitHub.