plandex-ai/plandex · error
error getting plan convo: %v
Error message
error getting plan convo: %v
What it means
The second goroutine in loadBuildFile, which fetches the plan conversation via db.GetPlanConvo, is guarded by a recover() defer. If that goroutine panics, the panic value is logged with a stack trace, wrapped as "error getting plan convo: %v", and sent to errCh before runtime.Goexit() prevents a duplicate send. This error signals a crash (not a normal db error) while loading the plan's conversation messages.
Source
Thrown at app/server/model/plan/build_load.go:273
activePlan.StreamDoneCh <- &shared.ApiError{
Type: shared.ApiErrorTypeOther,
Status: http.StatusInternalServerError,
Msg: "Error getting current plan state: " + err.Error(),
}
errCh <- fmt.Errorf("error getting current plan state: %v", err)
return
}
currentPlan = res
log.Println("Got current plan state")
errCh <- nil
}()
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in getPlanConvo: %v\n%s", r, debug.Stack())
errCh <- fmt.Errorf("error getting plan convo: %v", r)
runtime.Goexit() // don't allow outer function to continue and double-send to channel
}
}()
res, err := db.GetPlanConvo(currentOrgId, planId)
if err != nil {
log.Printf("Error getting plan convo: %v\n", err)
errCh <- fmt.Errorf("error getting plan convo: %v", err)
return
}
convo = res
errCh <- nil
}()
for i := 0; i < 2; i++ {
err = <-errCh
if err != nil {
log.Printf("Error getting plan data: %v\n", err)View on GitHub (pinned to e2d772072e)
Solutions
- Read the panic stack trace logged immediately before this error to locate the crashing line in db.GetPlanConvo
- Inspect the plan's convo messages for nil/malformed entries that could break the db layer
- Add nil/bounds guards in db.GetPlanConvo around message assembly
- Rebuild; the outer handler resets IsBuildingByPath and reports a 500 via StreamDoneCh
Example fix
// before
res, err := db.GetPlanConvo(currentOrgId, planId) // panics on nil message field
// after
for _, m := range rawMessages {
if m == nil {
continue // skip malformed rows instead of dereferencing
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if currentOrgId == "" || planId == "" {
return fmt.Errorf("cannot load plan convo: orgId and planId are required")
} Type guard
func hasConvoParams(orgId, planId string) bool {
return orgId != "" && planId != ""
} Try / catch
res, err := safeGetPlanConvo(currentOrgId, planId) // recovers panics internally
if err != nil {
log.Printf("plan convo unavailable, aborting load: %v", err)
return err
} Prevention
- Guard db.GetPlanConvo against nil fields in convo message rows
- Fuzz/deserialize convo rows defensively (skip malformed messages)
- Watch logs for "panic in getPlanConvo" and fix the underlying db-layer bug
- Test convo loading with empty and partial conversation histories
When it happens
Trigger: A panic happens inside the goroutine calling db.GetPlanConvo(currentOrgId, planId) — typically a nil pointer dereference or out-of-range access while assembling the conversation message list.
Common situations: Convo messages contain unexpected shapes (nil fields, malformed JSON deserialized into slices) causing db layer panics; concurrent writes to the convo table during read; a regression in db.GetPlanConvo after a code change.
Related errors
- panic in SyncPlanTokens: %v %s
- error getting current plan state: %v
- panic in UpdateContexts: %v\n%s
- panic in GetPlanConvo: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/64f4cf4f0557774c.
Report an issue: GitHub.