{"record":{"id":"0650d8f44ae96bb9","repo":"m1k1o/neko","slug":"unable-to-watch-file-transfer-dir-w","errorCode":null,"errorMessage":"unable to watch file transfer dir: %w","messagePattern":"unable to watch file transfer dir: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/plugins/filetransfer/manager.go","lineNumber":196,"sourceCode":"\t\t\t\t\tm.logger.Debug().Str(\"event\", e.String()).Msg(\"file transfer dir watcher event\")\n\n\t\t\t\t\terr, changed := m.refresh()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tm.logger.Err(err).Msg(\"unable to refresh file transfer list\")\n\t\t\t\t\t}\n\n\t\t\t\t\tif changed {\n\t\t\t\t\t\tm.broadcastUpdate()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase err := <-watcher.Errors:\n\t\t\t\tm.logger.Err(err).Msg(\"error in file transfer dir watcher\")\n\t\t\t}\n\t\t}\n\t}()\n\n\tif err := watcher.Add(m.config.RootDir); err != nil {\n\t\treturn fmt.Errorf(\"unable to watch file transfer dir: %w\", err)\n\t}\n\n\t// initial refresh\n\terr, changed := m.refresh()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to refresh file transfer list: %w\", err)\n\t}\n\tif changed {\n\t\tm.broadcastUpdate()\n\t}\n\n\treturn nil\n}\n\nfunc (m *Manager) deleteFileHandler(w http.ResponseWriter, r *http.Request) error {\n\tsession, ok := auth.GetSession(r)\n\tif !ok {\n\t\treturn utils.HttpUnauthorized(\"session not found\")","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/m1k1o/neko/blob/b0f01cedea68893e85a3fd852c0521238c285695/server/internal/plugins/filetransfer/manager.go#L178-L214","documentation":"Returned by Manager.Start when the watcher was created successfully but watcher.Add(m.config.RootDir) fails, i.e. the file-transfer root directory cannot be registered with fsnotify for change events.","triggerScenarios":"Start runs, NewWatcher succeeds, but watcher.Add on config.RootDir returns an error — most commonly RootDir does not exist or is unreadable (permission denied), or the per-user watch limit is exhausted.","commonSituations":"RootDir point to a path that was never created (only Mkdir attempted when IsNotExist, which can itself fail silently — only logged), RootDir owned by another user, or a container where the volume is mounted read-only.","solutions":["Verify config.RootDir exists and the server process has read/execute permission on it (ls -ld <dir>); create it manually with correct ownership if needed","Check the Mkdir error log line ('creating file transfer directory') — if it failed, fix parent-dir permissions","Raise inotify watch limits (fs.inotify.max_user_watches) if the error is ENOSPC/EMFILE","Ensure the volume isn't mounted read-only and correct m.config.RootDir in the plugin config if it points to the wrong path"],"exampleFix":"// before\nrootdir: /nonexistent/var/filetransfer\n\n// after\nsudo mkdir -p /var/filetransfer && sudo chown app:app /var/filetransfer\n# config\nrootdir: /var/filetransfer","handlingStrategy":"validation","validationCode":"info, err := os.Stat(m.config.RootDir)\nif err != nil {\n    if os.IsNotExist(err) {\n        if err := os.MkdirAll(m.config.RootDir, os.ModePerm); err != nil { return err }\n    } else { return err }\n} else if !info.IsDir() {\n    return fmt.Errorf(\"%s is not a directory\", m.config.RootDir)\n}\nif err := unix.Access(m.config.RootDir, unix.R_OK|unix.X_OK); err != nil {\n    return fmt.Errorf(\"no read access to %s: %w\", m.config.RootDir, err)\n}","typeGuard":null,"tryCatchPattern":"if err := m.Start(); err != nil {\n    if strings.Contains(err.Error(), \"unable to watch file transfer dir\") {\n        log.Error().Err(err).Str(\"dir\", m.config.RootDir).Msg(\"fix root dir existence/permissions\")\n    }\n    return err\n}","preventionTips":["Create RootDir at install time with the server user as owner","Point config.RootDir at a stable, non-removable, non-RO-mounted path","Include a startup readiness check that verifies RootDir is writable before Start"],"tags":["go","fsnotify","filesystem","permissions"],"backgroundTag":"watch-directory-unavailable","analyzedSha":"b0f01cedea68893e85a3fd852c0521238c285695","analyzedAt":"2026-09-01T10:35:56.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}