openclaw - ✅(Solved) Fix [Bug]: plugin discovery scans into node_modules/browser_data causing FD exhaustion and spawn EBADF (reproducible on 2026.3.11) [3 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#43813Fetched 2026-04-08 00:18:28
View on GitHub
Comments
1
Participants
2
Timeline
12
Reactions
0
Author
Participants
Timeline (top)
referenced ×8cross-referenced ×3commented ×1

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.

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.

Fix Action

Workaround

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

PR fix notes

PR #43815: fix(plugins): skip node_modules and other heavy dirs in plugin discovery scan

Description (problem / solution / changelog)

Fixes #43813.

Problem

shouldIgnoreScannedDirectory() in plugin discovery only skips .bak/.backup-/.disabled directories. When a workspace skill contains node_modules (e.g. a LanceDB plugin with 5,000+ files), discovery descends into every subdirectory, exhausting file descriptors on macOS and causing spawn EBADF on all exec calls.

Measured on 2026.3.11 (macOS arm64): 12,232 open FDs at gateway startup, dropping to 1,231 after manually removing node_modules.

The skills watcher (DEFAULT_SKILLS_WATCH_IGNORED) and memory watcher (IGNORED_MEMORY_WATCH_DIR_NAMES) already skip these directories — but the plugin discovery scanner does not.

Fix

Add IGNORED_SCAN_DIRS set to shouldIgnoreScannedDirectory(), aligned with the existing watcher ignore patterns. 2 files changed, 67 insertions.

Tests

Added integration test verifying that fake plugins inside node_modules/, .git/, .venv/, etc. are not discovered, while legitimate sibling plugins still are.

pnpm lint    → 0 warnings, 0 errors
pnpm tsgo    → passed
pnpm vitest run src/plugins/discovery.test.ts → 14 passed

Previously: #2532, #8869, #11181


🤖 AI-assisted (Cursor + Claude). Tested on a production OpenClaw 2026.3.11 gateway with real FD exhaustion. The author understands the change.

Made with Cursor

Changed files

  • src/plugins/discovery.test.ts (modified, +46/-0)
  • src/plugins/discovery.ts (modified, +29/-0)

PR #43859: fix(plugins): skip node_modules and other non-plugin directories during discovery

Description (problem / solution / changelog)

Summary

Fixes #43813

The plugin discovery scanner was recursively scanning into node_modules, .git, .venv, and browser_data directories when workspace skills contained them. This caused file descriptor exhaustion (12,000+ FDs) on macOS, leading to spawn EBADF errors on all subsequent exec tool calls.

Changes

Added IGNORED_SCAN_DIRECTORIES set to shouldIgnoreScannedDirectory() function, aligning with patterns already used by:

  • DEFAULT_SKILLS_WATCH_IGNORED in refresh.ts
  • IGNORED_MEMORY_WATCH_DIR_NAMES in manager-sync-ops.ts

Directories now skipped:

  • node_modules, .pnpm-store (package managers)
  • .git (version control)
  • dist, build (build outputs)
  • .venv, venv, __pycache__, .mypy_cache, .pytest_cache (Python)
  • .cache (general caches)
  • browser_data, .browser_data (Playwright/Puppeteer)

The legacy .bak/.backup-/.disabled patterns are preserved.

Testing

Added test case ignores node_modules and other non-plugin directories to prevent FD exhaustion that verifies:

  1. Real plugins in skill directories are discovered
  2. Fake plugins nested inside node_modules, .git, dist, .venv, browser_data, and .cache are NOT discovered

Before/After

Before: ~12,232 open FDs when a workspace skill has node_modules After: ~1,231 open FDs (normal range)

Changed files

  • src/plugins/discovery.test.ts (modified, +32/-0)
  • src/plugins/discovery.ts (modified, +35/-0)

PR #46159: fix(plugins): skip node_modules and other heavy directories in discovery (closes #43813)

Description (problem / solution / changelog)

Summary

  • Problem: Plugin discovery scanner descends into node_modules, .git, and other heavy directories, exhausting file descriptors (12k+ FDs) and causing spawn EBADF on all exec calls.
  • Why it matters: Any plugin with node_modules breaks all exec tool calls in the gateway process.
  • What changed: Added node_modules, .git, dist, .venv, and other common heavy directories to shouldIgnoreScannedDirectory(), consistent with the skills watcher ignore list.
  • What did NOT change (scope boundary): Plugin loading logic, valid plugin detection.

Change Type

  • Bug fix

Scope

  • Plugin / extension

Linked Issue/PR

  • Closes #43813

Security Impact

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Steps

  1. Install a plugin with node_modules (~5000 files)
  2. Start gateway
  3. Check FD count: lsof -p $(pgrep openclaw-gateway) | wc -l

Expected

  • FDs in hundreds, exec works

Actual (before fix)

  • 12000+ FDs, all exec calls fail with EBADF

Evidence

  • Failing test/log before + passing after

Human Verification

  • Verified scenarios: pnpm build + pnpm check + pnpm test all passing
  • Edge cases checked: valid plugins inside non-ignored dirs still discovered
  • What you did NOT verify: runtime end-to-end FD count testing

Compatibility / Migration

  • Backward compatible? Yes
  • Migration needed? No

Failure Recovery

  • How to disable/revert: revert this commit

Risks and Mitigations

  • Risk: a plugin nested inside an ignored directory name won't be found. Mitigation: no valid plugin would be inside node_modules/.git/etc.

This PR was AI-assisted (fully tested with pnpm build/check/test).

Changed files

  • docs/.generated/config-baseline.json (modified, +1696/-108)
  • src/context-engine/registry.ts (modified, +67/-9)
  • src/plugins/discovery.ts (modified, +31/-1)

Code Example

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

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

---

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;
}
RAW_BUFFERClick to expand / collapse

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

  • #2532 — original spawn EBADF tracking issue
  • #8869 — attempted fix for skills watcher venv ignore (closed as stale; maintainer noted: "If the underlying bug is still reproducible on current main, open a new focused fix PR")
  • #11181 — FD leak in workspace scanning on every message

extent analysis

Problem Summary

shouldIgnoreScannedDirectory() only skips a few “.bak” patterns, so the plugin discovery scanner walks into huge directories such as node_modules, .git, .venv, etc. The recursive scan opens thousands of files, exhausting the macOS file‑descriptor table and causing spawn EBADF errors.

Root Cause Analysis

  • src/plugins/discovery.ts → shouldIgnoreScannedDirectory() returns false for most directory names.
  • discoverInDirectory() calls this function for every sub‑folder and then opens each file with openBoundaryFileSync.
  • Because node_modules (and other heavy dirs) are not ignored, the scanner opens > 8 k files from that tree, blowing the FD limit.

Fix Plan

  1. Create a shared ignore list (so other parts of the codebase stay in sync).
  2. Extend shouldIgnoreScannedDirectory() to check against that list.
  3. Guard against accidental recursion depth (optional but cheap).
  4. Add unit tests to ensure the new patterns are ignored.
  5. Release a hot‑patch (no breaking API change).

1. Shared ignore constants

Create a new file src/plugins/ignoreLists.ts (or add to an existing constants module):

// src/plugins/ignoreLists.ts
export const PLUGIN_DISCOVERY_IGNORED_DIRS = [
  // patterns already used elsewhere
  'node_modules',
  '.git',
  '.venv',
  'venv',
  '__pycache__',
  '.mypy_cache',
  '.pytest_cache',
  'dist',
  'build',
  '.cache',
  // original .bak patterns (kept for backward compatibility)
  '.bak',
  '.backup-',
  '.disabled',
];

2. Updated shouldIgnoreScannedDirectory

Replace the old implementation with a case‑insensitive check against the list:

// src/plugins/discovery.ts
import { PLUGIN_DISCOVERY_IGNORED_DIRS } from './ignoreLists';

function shouldIgnoreScannedDirectory(dirName: string): boolean {
  const normalized = dirName.trim().toLowerCase();
  if (!normalized) return true;

  // Direct match (e.g. "node_modules

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - ✅(Solved) Fix [Bug]: plugin discovery scans into node_modules/browser_data causing FD exhaustion and spawn EBADF (reproducible on 2026.3.11) [3 pull requests, 1 comments, 2 participants]