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
- Read the attached stack trace (%s part) to locate the panicking line
- Add nil checks for plan/branch pointers before dereferencing in the listing loop
- Fix any data race by protecting shared plan/branch maps or re-fetching under lock
- 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
- Nil-check every plan/branch pointer before dereferencing in loops
- Avoid map/slice races by reading shared plan state under a lock
- Cover the listing path with tests using partially populated plans
- Keep the recover+Goexit pattern so panics never crash the server
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
- panic in UpdateContexts: %v\n%s
- panic in GetPlanConvo: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
- panic in SyncPlanTokens: %v %s
- panic in DeleteDraftPlans: %v %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/ede3aadad38c5632.
Report an issue: GitHub.