plandex-ai/plandex · critical

panic in ListPlansRunningHandler: %v %s

Error message

panic in ListPlansRunningHandler: %v
%s

What it means

This is the panic-recovery error emitted by the goroutine in ListPlansRunningHandler (plans_crud.go). When any panic occurs while listing plans/branches (e.g. nil pointer dereference on branch or plan data), the deferred recover converts it into an error 'panic in ListPlansRunningHandler: %v\n%s' with a stack trace, logs it, sends it on errCh, and calls runtime.Goexit to prevent a double channel send. The caller surfaces it as a 500 with the panic and stack attached.

Source

Thrown at app/server/handlers/plans_crud.go:448

		log.Printf("Error listing plans: %v\n", err)
		http.Error(w, "Error listing plans: "+err.Error(), http.StatusInternalServerError)
		return
	}

	var planIds []string
	for _, plan := range plans {
		planIds = append(planIds, plan.Id)
	}

	errCh := make(chan error, 2)
	var streams []*db.ModelStream
	var branches []*db.Branch

	go func() {
		defer func() {
			if r := recover(); r != nil {
				log.Printf("panic in ListPlansRunningHandler: %v\n%s", r, debug.Stack())
				errCh <- fmt.Errorf("panic in ListPlansRunningHandler: %v\n%s", r, debug.Stack())
				runtime.Goexit() // don't allow outer function to continue and double-send to channel
			}
		}()

		var err error
		if includeRecent {
			streams, err = db.GetActiveOrRecentModelStreams(planIds)
		} else {
			streams, err = db.GetActiveModelStreams(planIds)
		}
		if err != nil {
			errCh <- fmt.Errorf("error getting recent model streams: %v", err)
			return
		}
		errCh <- nil
	}()

	go func() {

View on GitHub (pinned to e2d772072e)

Solutions

  1. Read the attached stack trace (%s part) to locate the panicking line
  2. Add nil checks for plan/branch pointers before dereferencing in the listing loop
  3. Fix any data race by protecting shared plan/branch maps or re-fetching under lock
  4. Deploy the fix and retry the list request

Example fix

// before
for _, b := range plan.Branches {
    if b.IsRunning {
// after
for _, b := range plan.Branches {
    if b == nil {
        continue
    }
    if b.IsRunning {
}
Defensive patterns

Strategy: type-guard

Validate before calling

if plan == nil || plan.Branches == nil {
    errCh <- errors.New("plan or branches nil while listing running plans")
    return
}

Type guard

func safeBranch(b *db.Branch) *db.Branch {
    if b == nil {
        return &db.Branch{}
    }
    return b
}
// usage inside goroutine:
// b := safeBranch(branch); if b == nil || b.PlanId == "" { continue }

Try / catch

go func() {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("panic in ListPlansRunningHandler: %v\n%s", r, debug.Stack())
            errCh <- fmt.Errorf("panic in ListPlansRunningHandler: %v\n%s", r, debug.Stack())
            runtime.Goexit()
        }
    }()
    // ... listing logic with nil checks ...
}()

Prevention

When it happens

Trigger: Any panic inside the listing goroutine: nil pointer dereference when a branch or plan field is unexpectedly nil, index out of range while aggregating running plans, or a type assertion failure on plan/branch data (especially when includeRecent is true and recent-plan data is partially populated).

Common situations: Races between plan deletion/archival and listing; corrupted or partially written plan/branch records; upstream refactor changing return types so a nil slips through.

Related errors


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