{"record":{"id":"81aba6504caee89c","repo":"plandex-ai/plandex","slug":"error-creating-convo-dir-v","errorCode":null,"errorMessage":"error creating convo dir: %v","messagePattern":"error creating convo dir: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/convo_helpers.go","lineNumber":122,"sourceCode":"\n\tts := time.Now().UTC()\n\n\tif message.Id == \"\" {\n\t\tmessage.Id = uuid.New().String()\n\t}\n\n\tmessage.CreatedAt = ts\n\n\tbytes, err := json.Marshal(message)\n\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error marshalling convo message: %v\", err)\n\t}\n\n\terr = os.MkdirAll(convoDir, os.ModePerm)\n\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error creating convo dir: %v\", err)\n\t}\n\n\terr = os.WriteFile(filepath.Join(convoDir, message.Id+\".json\"), bytes, os.ModePerm)\n\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error writing convo message: %v\", err)\n\t}\n\n\terr = AddPlanConvoMessage(message, branch)\n\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error adding convo tokens: %v\", err)\n\t}\n\n\tvar desc string\n\tif message.Role == openai.ChatMessageRoleUser {\n\t\tdesc = \"💬 User prompt\"\n\t\t// TODO: add user name","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/convo_helpers.go#L104-L140","documentation":"After marshalling, StoreConvoMessage ensures the plan's convo directory exists with os.MkdirAll (perm os.ModePerm). This error wraps the MkdirAll failure, meaning the directory (and any missing parents) could not be created and the message was not persisted.","triggerScenarios":"os.MkdirAll(convoDir, os.ModePerm) fails: parent data-dir path missing on a read-only filesystem, permission denied on the parent directory, a regular file already exists at the convoDir path (MkdirAll returns ENOTDIR), path too long, or the volume is full/unavailable.","commonSituations":"Data volume mounted read-only after a container/storage change; data dir owned by another user (root after image rebuild); an operator accidentally created a file where the plan convo directory should be; disk full.","solutions":["Check the wrapped *PathError: if EACCES/EPERM fix ownership/permissions on the parent data directory (chown/chmod)","If ENOTDIR/EEXIST, remove the regular file occupying the convoDir path and let MkdirAll recreate it","Verify the data volume is mounted read-write (mount | grep, or container volume config)","Free disk space if the error indicates ENOSPC","Confirm the configured data-dir root exists and is writable by the server process"],"exampleFix":"// before\nmount -o ro /dev/sdb /plandex-data\n// after\nmount -o remount,rw /plandex-data\nchown -R plandex:plandex /plandex-data/plans","handlingStrategy":"validation","validationCode":"parent := filepath.Dir(getPlanConversationDir(orgId, planId))\nif info, err := os.Stat(parent); err != nil || !info.IsDir() {\n    return fmt.Errorf(\"plans data dir missing or not a directory: %s\", parent)\n}\nif err := syscall.Access(parent, os.O_WRONLY); err != nil {\n    return fmt.Errorf(\"data dir not writable: %w\", err)\n}","typeGuard":"func isMkdirPathError(err error) (*fs.PathError, bool) {\n    var pe *fs.PathError\n    return pe, errors.As(err, &pe) && pe.Op == \"mkdir\"\n}","tryCatchPattern":"id, err := db.StoreConvoMessage(repo, msg, userId, branch, false)\nif err != nil {\n    if strings.Contains(err.Error(), \"creating convo dir\") {\n        // check mount is rw and ownership, then retry once\n        if isVolumeReadOnly() { remountRW() }\n        id, err = db.StoreConvoMessage(repo, msg, userId, branch, false)\n    }\n    return err\n}","preventionTips":["Ensure the data volume is mounted read-write in container/volume config","Own the whole data dir with the server user","Never create files where the app expects directories","Alert on disk-full (ENOSPC) before it blocks message storage"],"tags":["go","filesystem","mkdir","permissions"],"backgroundTag":"mkdir-permission-denied","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"}