plandex-ai/plandex · error
panic in GetPlanConvo: %v\n%s
Error message
panic in GetPlanConvo: %v\n%s
What it means
Each per-file goroutine in GetPlanConvo is wrapped in a recover() so a panic while processing one convo file does not crash the server. The recovered panic value and stack trace are logged and converted into this error, which is then funneled to the caller through errCh. It indicates a bug or malformed input inside the file-processing goroutine, not an OS-level failure.
Source
Thrown at app/server/db/convo_helpers.go:43
files, err := os.ReadDir(convoDir)
if err != nil {
if os.IsNotExist(err) {
return convo, nil
}
return nil, fmt.Errorf("error reading convo dir: %v", err)
}
errCh := make(chan error, len(files))
convoCh := make(chan *ConvoMessage, len(files))
for _, file := range files {
go func(file os.DirEntry) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in GetPlanConvo: %v\n%s", r, debug.Stack())
errCh <- fmt.Errorf("panic in GetPlanConvo: %v\n%s", r, debug.Stack())
runtime.Goexit() // don't allow outer function to continue and double-send to channel
}
}()
bytes, err := os.ReadFile(filepath.Join(convoDir, file.Name()))
if err != nil {
errCh <- fmt.Errorf("error reading convo file: %v", err)
return
}
var convoMessage ConvoMessage
err = json.Unmarshal(bytes, &convoMessage)
if err != nil {
errCh <- fmt.Errorf("error unmarshalling convo file: %v", err)
return
}
View on GitHub (pinned to e2d772072e)
Solutions
- Read the stack trace printed after 'panic in GetPlanConvo:' in the server log to find the panicking line
- Inspect/remove the offending file in the convo directory (its name is usually identifiable from context)
- Upgrade Plandex to a version fixing the panic, or patch the panicking code path to handle nil/malformed data
- If it recurs, add a nil/type check before the operation shown in the stack
- Retry GetPlanConvo after removing the bad file — the error is per-file, not per-plan-storage
Example fix
// harden the processing body against nil data
// before
convoCh <- &convoMessage
// after
if convoMessage.Id == "" {
log.Printf("skipping convo file with no id: %s", file.Name())
return
}
convoCh <- &convoMessage Defensive patterns
Strategy: try-catch
Validate before calling
files, err := os.ReadDir(convoDir)
if err == nil {
for _, f := range files {
if fi, err := f.Info(); err == nil && fi.Size() == 0 {
log.Printf("warning: empty convo file %s", f.Name())
}
}
} Type guard
func isRecoveredPanicError(err error) bool {
return strings.HasPrefix(err.Error(), "panic in GetPlanConvo:")
} Try / catch
convo, err := db.GetPlanConvo(orgId, planId)
if err != nil {
if isRecoveredPanicError(err) {
// grab the stack trace embedded in the error and file a bug;
// retry once after isolating the bad file
return retryGetPlanConvo(orgId, planId)
}
return err
} Prevention
- Keep nil-checks on every ConvoMessage field used in display paths
- Add fuzz/unit tests that feed malformed JSON files through the convo reader
- Keep panic stacks from server logs (they are embedded in this error) for bug reports
- Pin Plandex versions and review changelogs for ConvoMessage schema changes
When it happens
Trigger: Any panic inside the goroutine body or its callees during os.ReadFile/Unmarshal handling — e.g. index-out-of-range on a nil field elsewhere, nil pointer dereference in ConvoMessage construction, or a future code change inside the loop that panics on unusual file content (very large file, zero-length file, weird file name).
Common situations: Corrupt or adversarial files dropped into the convo dir by an operator; a regression after upgrading Plandex that dereferences a nil field of ConvoMessage; concurrency bug (double channel send) that a developer partially fixed — the Goexit call exists precisely to prevent the double-send.
Related errors
- panic in UpdateContexts: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
- panic in DeleteDraftPlans: %v %s
- panic in GetFullCurrentPlanStateParams: %v\n%s
- panic in GetCurrentPlanState: %v\n%s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/9f48a3f9351e6c4c.
Report an issue: GitHub.