{"record":{"id":"b7ad22709232d938","repo":"IceWhaleTech/CasaOS","slug":"unmount-failed","errorCode":null,"errorMessage":"unmount failed","messagePattern":"unmount failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/utils/httper/drive.go","lineNumber":106,"sourceCode":"\t\treturn err\n\t}\n\tif res.StatusCode() != 200 {\n\t\treturn fmt.Errorf(\"mount failed\")\n\t}\n\tlogger.Info(\"mount then\", zap.Any(\"res\", res.Body()))\n\treturn nil\n}\nfunc Unmount(mountPoint string) error {\n\tres, err := NewRestyClient().R().SetFormData(map[string]string{\n\t\t\"mountPoint\": mountPoint,\n\t}).Post(\"/mount/unmount\")\n\tif err != nil {\n\t\tlogger.Error(\"when unmount\", zap.Error(err))\n\t\treturn err\n\t}\n\tif res.StatusCode() != 200 {\n\t\tlogger.Error(\"then unmount failed\", zap.Any(\"res\", res.Body()))\n\t\treturn fmt.Errorf(\"unmount failed\")\n\t}\n\n\tlogger.Info(\"unmount then\", zap.Any(\"res\", res.Body()))\n\treturn nil\n}\n\nfunc CreateConfig(data map[string]string, name, t string) error {\n\tdata[\"config_is_local\"] = \"false\"\n\tdataStr, _ := json.Marshal(data)\n\tres, err := NewRestyClient().R().SetFormData(map[string]string{\n\t\t\"name\":       name,\n\t\t\"parameters\": string(dataStr),\n\t\t\"type\":       t,\n\t}).Post(\"/config/create\")\n\tlogger.Info(\"when create config then\", zap.Any(\"res\", res.Body()))\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/IceWhaleTech/CasaOS/blob/0d3b2f444ec0193193cf03eef6d43c6e35b0183e/pkg/utils/httper/drive.go#L88-L124","documentation":"Returned by Unmount() in pkg/utils/httper/drive.go when POST /mount/unmount responds non-200. rclone refuses unmount when the given mountPoint is not currently mounted, or when files under it are held open by processes (EBUSY). The response body containing rclone's reason is logged but not included in the returned error.","triggerScenarios":"POST /mount/unmount with mountPoint that (1) was never mounted or already unmounted, (2) has open file handles (shell cwd inside the mount, running process reading files) so the kernel reports device busy, (3) rclone daemon restarted so its internal mount registry no longer knows the mount, (4) mountPoint string differs from the one used at mount time (trailing slash, symlinked path).","commonSituations":"CasaOS UI 'remove drive' clicked twice or after drive service restart; user has a terminal or file browser sitting inside the mount; stale entry in /proc/mounts after a hard reboot; path canonicalization mismatch between mount and unmount calls.","solutions":["Check whether the point is actually mounted before unmounting (unix.TestCheck or parse /proc/mounts).","Identify processes holding the mount with lsof +f -- <mountPoint> or fuser -vm <mountPoint> and close them.","If rclone lost track of the mount, unmount at the OS level: umount <mountPoint> (or fusermount -u / fusermount3 -uz for lazy), then retry.","Normalize the mountPoint string (filepath.Clean, consistent absolute path) so it matches what was passed to Mount().","Include status and body in the error for diagnosability."],"exampleFix":"// before\nif res.StatusCode() != 200 {\n    logger.Error(\"then unmount failed\", zap.Any(\"res\", res.Body()))\n    return fmt.Errorf(\"unmount failed\")\n}\n\n// after\nif res.StatusCode() != 200 {\n    logger.Error(\"then unmount failed\", zap.Any(\"res\", res.Body()))\n    return fmt.Errorf(\"unmount failed: status=%d body=%s\", res.StatusCode(), res.Body())\n}","handlingStrategy":"validation","validationCode":"// before calling Unmount()\ndata, err := os.ReadFile(\"/proc/mounts\")\nif err == nil && !strings.Contains(string(data), \" \"+mountPoint+\" \") {\n    return nil // idempotent: nothing to unmount\n}","typeGuard":null,"tryCatchPattern":"if err := httper.Unmount(mountPoint); err != nil {\n    if isMounted(mountPoint) {\n        // rclone failed but kernel still has it: force OS-level unmount\n        if uerr := exec.Command(\"fusermount\", \"-uz\", mountPoint).Run(); uerr != nil {\n            return fmt.Errorf(\"unmount failed (%v) and lazy unmount failed (%v)\", err, uerr)\n        }\n    }\n    return err\n}","preventionTips":["Make unmount idempotent by checking /proc/mounts first.","Close/cwd-out of processes under the mount before unmounting (lsof +f -- <point>).","Never blind-retry unmount on EBUSY; clear holders first.","Persist the exact mountPoint used at mount time and reuse it verbatim."],"tags":["rclone","unmount","fuse","http-client","casaos"],"backgroundTag":null,"analyzedSha":"0d3b2f444ec0193193cf03eef6d43c6e35b0183e","analyzedAt":"2026-08-15T13:27:57.821Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}