m1k1o/neko · error

unable to watch file transfer dir: %w

Error message

unable to watch file transfer dir: %w

What it means

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.

Source

Thrown at server/internal/plugins/filetransfer/manager.go:196

					m.logger.Debug().Str("event", e.String()).Msg("file transfer dir watcher event")

					err, changed := m.refresh()
					if err != nil {
						m.logger.Err(err).Msg("unable to refresh file transfer list")
					}

					if changed {
						m.broadcastUpdate()
					}
				}
			case err := <-watcher.Errors:
				m.logger.Err(err).Msg("error in file transfer dir watcher")
			}
		}
	}()

	if err := watcher.Add(m.config.RootDir); err != nil {
		return fmt.Errorf("unable to watch file transfer dir: %w", err)
	}

	// initial refresh
	err, changed := m.refresh()
	if err != nil {
		return fmt.Errorf("unable to refresh file transfer list: %w", err)
	}
	if changed {
		m.broadcastUpdate()
	}

	return nil
}

func (m *Manager) deleteFileHandler(w http.ResponseWriter, r *http.Request) error {
	session, ok := auth.GetSession(r)
	if !ok {
		return utils.HttpUnauthorized("session not found")

View on GitHub (pinned to b0f01cedea)

Solutions

  1. 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
  2. Check the Mkdir error log line ('creating file transfer directory') — if it failed, fix parent-dir permissions
  3. Raise inotify watch limits (fs.inotify.max_user_watches) if the error is ENOSPC/EMFILE
  4. Ensure the volume isn't mounted read-only and correct m.config.RootDir in the plugin config if it points to the wrong path

Example fix

// before
rootdir: /nonexistent/var/filetransfer

// after
sudo mkdir -p /var/filetransfer && sudo chown app:app /var/filetransfer
# config
rootdir: /var/filetransfer
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(m.config.RootDir)
if err != nil {
    if os.IsNotExist(err) {
        if err := os.MkdirAll(m.config.RootDir, os.ModePerm); err != nil { return err }
    } else { return err }
} else if !info.IsDir() {
    return fmt.Errorf("%s is not a directory", m.config.RootDir)
}
if err := unix.Access(m.config.RootDir, unix.R_OK|unix.X_OK); err != nil {
    return fmt.Errorf("no read access to %s: %w", m.config.RootDir, err)
}

Try / catch

if err := m.Start(); err != nil {
    if strings.Contains(err.Error(), "unable to watch file transfer dir") {
        log.Error().Err(err).Str("dir", m.config.RootDir).Msg("fix root dir existence/permissions")
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01). Data as JSON: /api/errors/0650d8f44ae96bb9. Report an issue: GitHub.