Skip to content

[Bug]: plugin discovery scans into node_modules/browser_data causing FD exhaustion and spawn EBADF (reproducible on 2026.3.11) #43813

@zhangyi-3

Description

@zhangyi-3

Summary

shouldIgnoreScannedDirectory() in src/plugins/discovery.ts only skips .bak/.backup-/.disabled directories. It does not skip node_modules, .git, .venv, browser_data, or any other heavy dependency/build directories.

When a workspace skill or plugin contains node_modules, the plugin discovery scanner descends into every subdirectory and probes for package manifests via openBoundaryFileSync. This exhausts the process file descriptor table on macOS and causes spawn EBADF (errno -9) on all subsequent exec tool calls.

The skills watcher (DEFAULT_SKILLS_WATCH_IGNORED in refresh.ts) and memory watcher (IGNORED_MEMORY_WATCH_DIR_NAMES in manager-sync-ops.ts) already ignore these directories — but the plugin discovery scanner does not.

Environment

  • OpenClaw: 2026.3.11 (29dc654) — latest release
  • OS: macOS (Darwin, arm64)
  • Node.js: v22.22.1

Steps to Reproduce

  1. Install a skill/plugin that has its own node_modules (e.g. memory-lancedb-pro with ~5,000 files in node_modules/)
  2. Start the gateway: openclaw gateway start
  3. Check FD count: lsof -p $(pgrep openclaw-gateway) | wc -l
  4. Observe 12,000+ open file descriptors, mostly REG (regular file) in read-only mode
  5. Attempt any exec tool call → fails with spawn EBADF

Expected Behavior

Plugin discovery should skip node_modules, .git, .venv, and other directories that cannot contain valid plugins. FD count should stay in the hundreds, not thousands.

Actual Behavior

$ lsof -p $(pgrep openclaw-gateway) | wc -l
   12232

$ lsof -p $(pgrep openclaw-gateway) | awk '{print $NF}' | grep node_modules | wc -l
   8595

All exec calls fail with spawn EBADF.

Root Cause

src/plugins/discovery.ts, function shouldIgnoreScannedDirectory():

function shouldIgnoreScannedDirectory(dirName: string): boolean {
  const normalized = dirName.trim().toLowerCase();
  if (!normalized) return true;
  if (normalized.endsWith(".bak")) return true;       // ← only these
  if (normalized.includes(".backup-")) return true;   // ← three
  if (normalized.includes(".disabled")) return true;  // ← patterns
  return false;
}

This function is called by discoverInDirectory() which recurses into every subdirectory of plugin roots. Without node_modules in the skip list, it enters dependency trees with thousands of packages.

Compare with the skills watcher (DEFAULT_SKILLS_WATCH_IGNORED) which already ignores node_modules, .git, dist, .venv, venv, __pycache__, .mypy_cache, .pytest_cache, build, .cache.

Workaround

Manually delete node_modules from workspace skills and restart the gateway. FD count drops from 12,232 to 1,231.

Previously

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions