m1k1o/neko · error

unable to start file transfer dir watcher: %w

Error message

unable to start file transfer dir watcher: %w

What it means

Returned by filetransfer Manager.Start when fsnotify.NewWatcher() itself fails, so the directory watcher that keeps the file-transfer listing fresh cannot even be created. This is an OS/resource-level failure, not a problem with the configured directory.

Source

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

func (m *Manager) Start() error {
	// send init message once a user connects
	m.sessions.OnConnected(func(session types.Session) {
		m.sendUpdate(session)
	})

	// if file transfer is disabled, return immediately without starting the watcher
	if !m.config.Enabled {
		return nil
	}

	if _, err := os.Stat(m.config.RootDir); os.IsNotExist(err) {
		err = os.Mkdir(m.config.RootDir, os.ModePerm)
		m.logger.Err(err).Msg("creating file transfer directory")
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("unable to start file transfer dir watcher: %w", err)
	}

	go func() {
		defer watcher.Close()

		// periodically refresh file list
		ticker := time.NewTicker(m.config.RefreshInterval)
		defer ticker.Stop()

		for {
			select {
			case <-m.shutdown:
				m.logger.Info().Msg("shutting down file transfer manager")
				return
			case <-ticker.C:
				err, changed := m.refresh()
				if err != nil {
					m.logger.Err(err).Msg("unable to refresh file transfer list")

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Check and raise inotify limits: sysctl fs.inotify.max_user_watches and fs.inotify.max_user_instances (e.g. to 524288 / 512)
  2. Raise the process file-descriptor limit (ulimit -n / systemd LimitNOFILE)
  3. Verify the kernel/container supports inotify; in restricted containers run with a host-matching /proc/sys/fs/inotify
  4. Retry Start after freeing resources; if it persists, capture the wrapped %w error for the exact errno

Example fix

// before
$ ulimit -n   # 256

// after
$ ulimit -n 65536
$ sudo sysctl -w fs.inotify.max_user_watches=524288
Defensive patterns

Strategy: fallback

Validate before calling

// preflight check for inotify availability
if _, err := os.Stat("/proc/sys/fs/inotify/max_user_watches"); err != nil {
    log.Warn().Msg("inotify unavailable; file listing may be stale")
}
if n := readInt("/proc/sys/fs/inotify/max_user_instances"); n > 0 && n <= usedInstances() {
    return fmt.Errorf("inotify instance limit reached (%d)", n)
}

Try / catch

if err := m.Start(); err != nil {
    if strings.Contains(err.Error(), "unable to start file transfer dir watcher") {
        log.Error().Err(err).Msg("increase fs.inotify limits or fd ulimit")
        // degrade: run with periodic polling instead of a watcher
    }
    return err
}

Prevention

When it happens

Trigger: Manager.Start runs and fsnotify.NewWatcher() returns an error — typically fd/inotify watch limits exhausted (EMFILE/ENOSPC), or running in a container/kernel without inotify support.

Common situations: Long-running servers hitting fs.inotify.max_user_watches / max_user_instances limits; minimal containers or sandboxes without inotify; low file-descriptor ulimits.

Related errors


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