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

  1. Read the panic stack trace logged immediately before this error to locate the crashing line in db.GetPlanConvo
  2. Inspect the plan's convo messages for nil/malformed entries that could break the db layer
  3. Add nil/bounds guards in db.GetPlanConvo around message assembly
  4. 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

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


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