plandex-ai/plandex · error
panic in DeleteOwnerPlans: %v %s
Error message
panic in DeleteOwnerPlans: %v %s
What it means
DeleteOwnerPlans deletes each plan's directory in a separate goroutine. If any goroutine panics (e.g. inside DeletePlanDir), the deferred recover() logs the panic with a stack trace, converts it into this error, sends it to errCh, and calls runtime.Goexit() so the worker doesn't double-send. The caller surfaces the panic as a normal error value instead of crashing the process.
Source
Thrown at app/server/db/plan_helpers.go:426
// 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 DeleteOwnerPlans: %v\n%s", r, debug.Stack())
errCh <- fmt.Errorf("panic in DeleteOwnerPlans: %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 plan dir: %v", err)
}
}
if len(ids) > 0 {
log.Println("Deleted", len(ids), "plans")
}
View on GitHub (pinned to e2d772072e)
Solutions
- Read the attached stack trace to find the panicking line inside DeletePlanDir and fix the underlying nil/pointer bug
- Check the plan id and orgId values passed to DeleteOwnerPlans for empty or invalid inputs
- Re-run after fixing; the error is per-plan, so other plans in the batch may still have been processed
- If panics recur, add validation before spawning goroutines instead of relying on recover
Example fix
// before
errCh <- DeletePlanDir(orgId, planId)
// after
if planId == "" {
errCh <- fmt.Errorf("cannot delete plan: empty planId")
return
}
errCh <- DeletePlanDir(orgId, planId) Defensive patterns
Strategy: try-catch
Validate before calling
if planId == "" || orgId == "" {
return fmt.Errorf("invalid args: orgId/planId must be non-empty")
} Try / catch
err := DeleteOwnerPlans(orgId, ids)
if err != nil && strings.Contains(err.Error(), "panic in DeleteOwnerPlans") {
log.Printf("worker panic during plan deletion: %v", err)
// retry or alert; panic was already recovered inside the library
} Prevention
- Validate orgId and plan ids before calling DeleteOwnerPlans
- Keep DeletePlanDir free of shared mutable state to avoid data races
- Add unit tests covering empty/invalid plan directories
- Monitor the logs for 'panic in DeleteOwnerPlans' to catch underlying bugs early
When it happens
Trigger: A panic is raised inside DeletePlanDir (called as `DeletePlanDir(orgId, planId)` in the spawned goroutine) — e.g. nil pointer dereference, out-of-range slice access, or an unexpected nil from a lower layer — while iterating over plan ids.
Common situations: Corrupted or unexpected plan directory state on disk, races on shared state inside DeletePlanDir, or a nil map/pointer bug introduced by a recent change to DeletePlanDir.
Related errors
- failed to read the file %s: %v
- error getting plan current branch: %v
- panic in UpdateContexts: %v\n%s
- panic in GetPlanConvo: %v\n%s
- error reading convo files: %v
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/4b642aa228c8f2d9.
Report an issue: GitHub.