{"record":{"id":"6c9076020016c628","repo":"plandex-ai/plandex","slug":"error-deleting-draft-plan-dir-v","errorCode":null,"errorMessage":"error deleting draft plan dir: %v","messagePattern":"error deleting draft plan dir: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/plan_helpers.go","lineNumber":389,"sourceCode":"\n\terrCh := make(chan error, len(ids))\n\tfor _, planId := range ids {\n\t\tgo func(planId string) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Printf(\"panic in DeleteDraftPlans: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\terrCh <- fmt.Errorf(\"panic in DeleteDraftPlans: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\truntime.Goexit() // don't allow outer function to continue and double-send to channel\n\t\t\t\t}\n\t\t\t}()\n\t\t\terrCh <- DeletePlanDir(orgId, planId)\n\t\t}(planId)\n\t}\n\n\tfor i := 0; i < len(ids); i++ {\n\t\terr := <-errCh\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error deleting draft plan dir: %v\", err)\n\t\t}\n\t}\n\n\tif len(ids) > 0 {\n\t\tlog.Println(\"Deleted\", len(ids), \"draft plans\")\n\t}\n\n\treturn nil\n}\n\nfunc DeleteOwnerPlans(orgId, projectId, userId string) error {\n\tres, err := Conn.Query(\"DELETE FROM plans WHERE project_id = $1 AND owner_id = $2 RETURNING id;\", projectId, userId)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error deleting plans: %v\", err)\n\t}\n\n\tdefer res.Close()\n","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/plan_helpers.go#L371-L407","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nreturn fmt.Errorf(\"error deleting draft plan dir: %v\", err)\n// after\nif os.IsNotExist(err) {\n    return nil\n}\nreturn fmt.Errorf(\"error deleting draft plan dir: %w\", err)","handlingStrategy":"fallback","validationCode":"if _, err := os.Stat(planDir); err == nil {\n    // dir exists and can be deleted\n}","typeGuard":null,"tryCatchPattern":"err := DeleteDraftPlans(orgId, projectId, userId)\nif err != nil {\n    var pe *fs.PathError\n    if errors.As(err, &pe) && errors.Is(pe.Err, syscall.ENOENT) {\n        // already deleted; safe to ignore\n    } else {\n        return err\n    }\n}","preventionTips":["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"],"tags":["filesystem","concurrency","cleanup","go"],"backgroundTag":"directory-delete-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}