plandex-ai/plandex · error

error reading convo files: %v

Error message

error reading convo files: %v

What it means

GetPlanConvo collects len(files) results from buffered errCh/convoCh channels; the first error received on errCh aborts the whole read and is wrapped as 'error reading convo files'. It is the aggregator for all per-goroutine failures — read errors (342), unmarshal errors (343), or recovered panics (341) — so the real cause is the inner wrapped error.

Source

Thrown at app/server/db/convo_helpers.go:70

			}

			var convoMessage ConvoMessage
			err = json.Unmarshal(bytes, &convoMessage)

			if err != nil {
				errCh <- fmt.Errorf("error unmarshalling convo file: %v", err)
				return
			}

			convoCh <- &convoMessage

		}(file)
	}

	for i := 0; i < len(files); i++ {
		select {
		case err := <-errCh:
			return nil, fmt.Errorf("error reading convo files: %v", err)
		case convoMessage := <-convoCh:
			convo = append(convo, convoMessage)
		}
	}

	sort.Slice(convo, func(i, j int) bool {
		return convo[i].CreatedAt.Before(convo[j].CreatedAt)
	})

	return convo, nil
}

func GetConvoMessage(orgId, planId, messageId string) (*ConvoMessage, error) {
	convoDir := getPlanConversationDir(orgId, planId)

	filePath := filepath.Join(convoDir, messageId+".json")

	bytes, err := os.ReadFile(filePath)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Read the inner error embedded in this message to identify the failing file and root cause (missing, corrupt, or panic)
  2. Delete or repair the specific bad file in the convo directory
  3. Fix permissions/ownership on the convo directory contents
  4. Restore the plan's convo directory from backup if multiple files are damaged
  5. Stop concurrent writers/deleters of the same data dir (single server instance per data dir)
Defensive patterns

Strategy: try-catch

Validate before calling

files, err := os.ReadDir(convoDir)
if err == nil {
    for _, f := range files {
        if data, err := os.ReadFile(filepath.Join(convoDir, f.Name())); err == nil {
            var m db.ConvoMessage
            if err := json.Unmarshal(data, &m); err != nil {
                log.Printf("precheck: bad convo file %s: %v", f.Name(), err)
            }
        }
    }
}

Type guard

func isConvoAggregateErr(err error) bool {
    return strings.HasPrefix(err.Error(), "error reading convo files:")
}

Try / catch

convo, err := db.GetPlanConvo(orgId, planId)
if err != nil {
    if isConvoAggregateErr(err) {
        inner := strings.TrimPrefix(err.Error(), "error reading convo files: ")
        log.Printf("plan convo degraded, inner cause: %s", inner)
    }
    return err
}

Prevention

When it happens

Trigger: Any of the per-file goroutines fails: a convo file deleted/corrupt/unreadable, a panic in a worker, or (because select returns on the first error) any single bad file poisons the entire convo read even when other files are fine.

Common situations: One corrupt message JSON making the whole plan history unviewable after a crash; a race with plan deletion during concurrent access; wrong permissions on a single file after restore; mixed old/new schema files after an upgrade.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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