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

  1. Read the attached stack trace to find the panicking line inside DeletePlanDir and fix the underlying nil/pointer bug
  2. Check the plan id and orgId values passed to DeleteOwnerPlans for empty or invalid inputs
  3. Re-run after fixing; the error is per-plan, so other plans in the batch may still have been processed
  4. 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

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


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/4b642aa228c8f2d9. Report an issue: GitHub.