From f2472edae641b28f265918d5ce418d153197b287 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 29 Jan 2026 13:42:33 +0800 Subject: [PATCH] fix: exclude Python virtual environments from skill scanner - Add .venv, venv, env to ignored patterns - Add __pycache__, .pytest_cache, .tox, .egg-info to ignored patterns - Prevents FD exhaustion when skills contain Python virtual environments - Fixes #3708: spawn EBADF errors from file descriptor leak The skill scanner was opening thousands of files in Python virtual environments without closing them, causing immediate FD exhaustion (~11,000 open FDs). This change adds common Python dependency directories to the ignore list, similar to how node_modules and .git are already ignored. Added comprehensive test cases to verify all Python-related directories are properly ignored. --- src/agents/skills/refresh.test.ts | 28 ++++++++++++++++++++++++++++ src/agents/skills/refresh.ts | 9 +++++++++ 2 files changed, 37 insertions(+) diff --git a/src/agents/skills/refresh.test.ts b/src/agents/skills/refresh.test.ts index 51b86e7f7..9925abe01 100644 --- a/src/agents/skills/refresh.test.ts +++ b/src/agents/skills/refresh.test.ts @@ -28,4 +28,32 @@ describe("ensureSkillsWatcher", () => { expect(ignored.some((re) => re.test("/tmp/workspace/skills/.git/config"))).toBe(true); expect(ignored.some((re) => re.test("/tmp/.hidden/skills/index.md"))).toBe(false); }); + + it("ignores Python virtual environments and cache directories", async () => { + const mod = await import("./refresh.js"); + const ignored = mod.DEFAULT_SKILLS_WATCH_IGNORED; + + // Python virtual environments + expect( + ignored.some((re) => re.test("/tmp/workspace/skills/.venv/lib/python3.9/site-packages/")), + ).toBe(true); + expect( + ignored.some((re) => re.test("/tmp/workspace/skills/venv/lib/python3.9/site-packages/")), + ).toBe(true); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/env/bin/python"))).toBe(true); + + // Python cache directories + expect( + ignored.some((re) => re.test("/tmp/workspace/skills/__pycache__/module.cpython-39.pyc")), + ).toBe(true); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/.pytest_cache/"))).toBe(true); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/.tox/py39/lib/"))).toBe(true); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/package.egg-info/PKG-INFO"))).toBe( + true, + ); + + // Should not ignore files that just contain these names + expect(ignored.some((re) => re.test("/tmp/workspace/skills/my_venv_config.json"))).toBe(false); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/pytest.ini"))).toBe(false); + }); }); diff --git a/src/agents/skills/refresh.ts b/src/agents/skills/refresh.ts index 489fd2e63..b41ffab0b 100644 --- a/src/agents/skills/refresh.ts +++ b/src/agents/skills/refresh.ts @@ -31,6 +31,15 @@ export const DEFAULT_SKILLS_WATCH_IGNORED: RegExp[] = [ /(^|[\\/])\.git([\\/]|$)/, /(^|[\\/])node_modules([\\/]|$)/, /(^|[\\/])dist([\\/]|$)/, + // Python virtual environments + /(^|[\\/])\.venv([\\/]|$)/, + /(^|[\\/])venv([\\/]|$)/, + /(^|[\\/])env([\\/]|$)/, + // Python cache directories + /(^|[\\/])__pycache__([\\/]|$)/, + /(^|[\\/])\.pytest_cache([\\/]|$)/, + /(^|[\\/])\.tox([\\/]|$)/, + /(^|[\\/])[^/\\]*\.egg-info([\\/]|$)/, ]; function bumpVersion(current: number): number {