{"record":{"id":"71db3f8d5fd5f6c2","repo":"plandex-ai/plandex","slug":"error-reading-convo-files-v","errorCode":null,"errorMessage":"error reading convo files: %v","messagePattern":"error reading convo files: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/convo_helpers.go","lineNumber":70,"sourceCode":"\t\t\t}\n\n\t\t\tvar convoMessage ConvoMessage\n\t\t\terr = json.Unmarshal(bytes, &convoMessage)\n\n\t\t\tif err != nil {\n\t\t\t\terrCh <- fmt.Errorf(\"error unmarshalling convo file: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconvoCh <- &convoMessage\n\n\t\t}(file)\n\t}\n\n\tfor i := 0; i < len(files); i++ {\n\t\tselect {\n\t\tcase err := <-errCh:\n\t\t\treturn nil, fmt.Errorf(\"error reading convo files: %v\", err)\n\t\tcase convoMessage := <-convoCh:\n\t\t\tconvo = append(convo, convoMessage)\n\t\t}\n\t}\n\n\tsort.Slice(convo, func(i, j int) bool {\n\t\treturn convo[i].CreatedAt.Before(convo[j].CreatedAt)\n\t})\n\n\treturn convo, nil\n}\n\nfunc GetConvoMessage(orgId, planId, messageId string) (*ConvoMessage, error) {\n\tconvoDir := getPlanConversationDir(orgId, planId)\n\n\tfilePath := filepath.Join(convoDir, messageId+\".json\")\n\n\tbytes, err := os.ReadFile(filePath)","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/convo_helpers.go#L52-L88","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the inner error embedded in this message to identify the failing file and root cause (missing, corrupt, or panic)","Delete or repair the specific bad file in the convo directory","Fix permissions/ownership on the convo directory contents","Restore the plan's convo directory from backup if multiple files are damaged","Stop concurrent writers/deleters of the same data dir (single server instance per data dir)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"files, err := os.ReadDir(convoDir)\nif err == nil {\n    for _, f := range files {\n        if data, err := os.ReadFile(filepath.Join(convoDir, f.Name())); err == nil {\n            var m db.ConvoMessage\n            if err := json.Unmarshal(data, &m); err != nil {\n                log.Printf(\"precheck: bad convo file %s: %v\", f.Name(), err)\n            }\n        }\n    }\n}","typeGuard":"func isConvoAggregateErr(err error) bool {\n    return strings.HasPrefix(err.Error(), \"error reading convo files:\")\n}","tryCatchPattern":"convo, err := db.GetPlanConvo(orgId, planId)\nif err != nil {\n    if isConvoAggregateErr(err) {\n        inner := strings.TrimPrefix(err.Error(), \"error reading convo files: \")\n        log.Printf(\"plan convo degraded, inner cause: %s\", inner)\n    }\n    return err\n}","preventionTips":["Parse the inner error to find the single bad file before touching storage","Quarantine rather than delete corrupt files","Ensure single-writer access to the plan data dir","Test convo reads after every crash/restore event"],"tags":["go","filesystem","json","concurrency"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}