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

  1. Read the logged stack trace to find the panicking frame in DeletePlanDir
  2. Guard against empty/invalid planId before spawning goroutines
  3. Make DeletePlanDir return errors instead of panicking on bad input
  4. 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

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


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