{"record":{"id":"d6193c770b5bf175","repo":"plandex-ai/plandex","slug":"error-reading-convo-message-v","errorCode":null,"errorMessage":"error reading convo message: %v","messagePattern":"error reading convo message: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/convo_helpers.go","lineNumber":90,"sourceCode":"\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)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading convo message: %v\", err)\n\t}\n\n\tvar convoMessage ConvoMessage\n\terr = json.Unmarshal(bytes, &convoMessage)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling convo message: %v\", err)\n\t}\n\n\treturn &convoMessage, nil\n}\n\nfunc StoreConvoMessage(repo *GitRepo, message *ConvoMessage, currentUserId, branch string, commit bool) (string, error) {\n\tconvoDir := getPlanConversationDir(message.OrgId, message.PlanId)\n\n\tts := time.Now().UTC()\n\n\tif message.Id == \"\" {\n\t\tmessage.Id = uuid.New().String()","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/convo_helpers.go#L72-L108","documentation":"GetConvoMessage reads a single message file (<messageId>.json) from the plan's convo directory with os.ReadFile and returns this error wrapping the *PathError on failure. Note there is no IsNotExist special case here (unlike GetPlanConvo), so a missing message — the most common cause — surfaces as this error too.","triggerScenarios":"os.ReadFile fails: the messageId does not exist (client passed a stale/deleted/wrong UUID, or the '.json' suffix handling was bypassed), wrong orgId/planId used to build the path, permission denied, or I/O error on the storage volume.","commonSituations":"Client or UI holding a reference to a message from before a plan was reset/rolled back; a cross-plan or cross-org message id used by mistake; files lost after an incomplete restore; permissions changed on the data dir.","solutions":["Confirm the messageId (and orgId/planId) is correct and the message still exists: ls <convoDir>/<messageId>.json","If the message was deleted by a plan reset/rollback, treat it as gone and refresh the convo listing","Fix permissions/ownership on the convo directory if the underlying error is permission-denied","Restore the file from backup if it was lost","Check the storage volume for I/O errors if the underlying message indicates a hardware/filesystem issue"],"exampleFix":"// caller hardening: treat not-found distinctly\n// before\nmsg, err := db.GetConvoMessage(orgId, planId, msgId)\n// after\npath := filepath.Join(db.GetPlanConversationDir(orgId, planId), msgId+\".json\")\nif _, err := os.Stat(path); os.IsNotExist(err) {\n    return fmt.Errorf(\"message %s not found\", msgId)\n}\nmsg, err := db.GetConvoMessage(orgId, planId, msgId)","handlingStrategy":"type-guard","validationCode":"if messageId == \"\" || !uuid.IsValid(messageId) {\n    return fmt.Errorf(\"invalid message id %q\", messageId)\n}\npath := filepath.Join(getPlanConversationDir(orgId, planId), messageId+\".json\")\nif _, err := os.Stat(path); err != nil {\n    if os.IsNotExist(err) {\n        return ErrConvoMessageNotFound\n    }\n}","typeGuard":"func isConvoMessageNotFound(err error) bool {\n    return errors.Is(err, fs.ErrNotExist) || strings.Contains(err.Error(), \"no such file\")\n}","tryCatchPattern":"msg, err := db.GetConvoMessage(orgId, planId, messageId)\nif err != nil {\n    if isConvoMessageNotFound(err) {\n        return http.StatusNotFound, fmt.Errorf(\"message %s not found\", messageId)\n    }\n    return http.StatusInternalServerError, err\n}","preventionTips":["Validate message ids as UUIDs before lookup","Refresh client-side message references after plan resets/rollbacks","Keep orgId/planId scoping correct to avoid cross-plan lookups","Alert on permission errors in the data dir"],"tags":["go","filesystem","io","file-not-found"],"backgroundTag":"file-not-found","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}