plandex-ai/plandex · error
panic in DeleteDraftPlans: %v %s
Error message
panic in DeleteDraftPlans: %v %s
What it means
DeleteDraftPlans spawns goroutines to delete each draft plan's directory. This message is both logged and sent to errCh when one of those goroutines panics; the deferred recover captures it, and runtime.Goexit prevents double-sending to the channel. It indicates an unexpected runtime fault (nil map write, index out of range, nil pointer) inside DeletePlanDir.
Source
Thrown at app/server/db/plan_helpers.go:378
// get ids
var ids []string
for res.Next() {
var id string
err := res.Scan(&id)
if err != nil {
return fmt.Errorf("error scanning deleted draft plan id: %v", err)
}
ids = append(ids, id)
}
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")
}
View on GitHub (pinned to e2d772072e)
Solutions
- Read the logged stack trace to find the panicking frame in DeletePlanDir
- Guard against empty/invalid planId before spawning goroutines
- Make DeletePlanDir return errors instead of panicking on bad input
- Handle symlink cycles with proper path validation before os.RemoveAll
Example fix
// before
errCh <- DeletePlanDir(orgId, planId)
// after
if planId == "" {
errCh <- fmt.Errorf("empty planId")
return
}
errCh <- DeletePlanDir(orgId, planId) Defensive patterns
Strategy: try-catch
Validate before calling
if planId == "" {
return fmt.Errorf("cannot delete plan dir: empty planId")
} Try / catch
if err := DeleteDraftPlans(orgId, projectId, userId); err != nil {
if strings.Contains(err.Error(), "panic in DeleteDraftPlans") {
// inspect the attached debug.Stack() to find the panicking frame
}
return err
} Prevention
- Make DeletePlanDir error-returning, never panicking
- Validate planId (non-empty, no path traversal) before goroutine spawn
- Avoid symlink cycles in plan directories
- Keep recover+Goexit pattern intact to prevent double channel sends
When it happens
Trigger: A goroutine executing DeletePlanDir(orgId, planId) panics: nil dereference inside path handling, os operations with malformed planId, or an unrecovered panic deeper in the call stack.
Common situations: planId containing path-traversal/empty values causing odd filesystem behavior; a bug introduced in DeletePlanDir; concurrent map access elsewhere; stack exhaustion from recursive delete loops (symlink cycles).
Related errors
- panic in UpdateContexts: %v\n%s
- panic in GetPlanConvo: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
- panic in GetFullCurrentPlanStateParams: %v\n%s
- panic in GetCurrentPlanState: %v\n%s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/c40faefd58f099ba.
Report an issue: GitHub.