From 6ed255319f1a33e0519dcc289c84fd6bc31625e0 Mon Sep 17 00:00:00 2001 From: "clawdinator[bot]" <253378751+clawdinator[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 06:41:53 +0000 Subject: [PATCH] fix(skills): ignore Python venvs and caches in skills watcher (#12399) * fix(skills): ignore Python venvs and caches in skills watcher Add .venv, venv, __pycache__, .mypy_cache, .pytest_cache, build, and .cache to the default ignored patterns for the skills watcher. This prevents file descriptor exhaustion when a skill contains a Python virtual environment with tens of thousands of files, which was causing EBADF spawn errors on macOS. Fixes #1056 Co-Authored-By: Claude Opus 4.5 * docs: add changelog entry for skills watcher ignores * docs: fill changelog PR number --------- Co-authored-by: Kyle Howells Co-authored-by: Claude Opus 4.5 Co-authored-by: CLAWDINATOR Bot --- CHANGELOG.md | 2 ++ src/agents/skills/refresh.test.ts | 26 +++++++++++++++++++++++++- src/agents/skills/refresh.ts | 9 +++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 485b5f08e53..31b5f653403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,8 @@ Docs: https://docs.openclaw.ai - Heartbeat: allow explicit accountId routing for multi-account channels. (#8702) Thanks @lsh411. - Routing: refresh bindings per message by loading config at route resolution so binding changes apply without restart. (#11372) Thanks @juanpablodlc. - TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras. +- Security: stop exposing Gateway auth tokens via URL query parameters in Control UI entrypoints, and reject hook tokens in query parameters. (#9436) Thanks @coygeek. +- Skills: ignore Python venvs and common cache/build folders in the skills watcher to prevent FD exhaustion. (#12399) Thanks @kylehowells. - Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard. - Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo. - Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman. diff --git a/src/agents/skills/refresh.test.ts b/src/agents/skills/refresh.test.ts index 51b86e7f795..30fdfa8388e 100644 --- a/src/agents/skills/refresh.test.ts +++ b/src/agents/skills/refresh.test.ts @@ -12,7 +12,7 @@ vi.mock("chokidar", () => { }); describe("ensureSkillsWatcher", () => { - it("ignores node_modules, dist, and .git by default", async () => { + it("ignores node_modules, dist, .git, and Python venvs by default", async () => { const mod = await import("./refresh.js"); mod.ensureSkillsWatcher({ workspaceDir: "/tmp/workspace" }); @@ -21,11 +21,35 @@ describe("ensureSkillsWatcher", () => { expect(opts.ignored).toBe(mod.DEFAULT_SKILLS_WATCH_IGNORED); const ignored = mod.DEFAULT_SKILLS_WATCH_IGNORED; + + // Node/JS paths expect(ignored.some((re) => re.test("/tmp/workspace/skills/node_modules/pkg/index.js"))).toBe( true, ); expect(ignored.some((re) => re.test("/tmp/workspace/skills/dist/index.js"))).toBe(true); expect(ignored.some((re) => re.test("/tmp/workspace/skills/.git/config"))).toBe(true); + + // Python virtual environments and caches + expect(ignored.some((re) => re.test("/tmp/workspace/skills/scripts/.venv/bin/python"))).toBe( + true, + ); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/venv/lib/python3.10/site.py"))).toBe( + true, + ); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/__pycache__/module.pyc"))).toBe( + true, + ); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/.mypy_cache/3.10/foo.json"))).toBe( + true, + ); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/.pytest_cache/v/cache"))).toBe(true); + + // Build artifacts and caches + expect(ignored.some((re) => re.test("/tmp/workspace/skills/build/output.js"))).toBe(true); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/.cache/data.json"))).toBe(true); + + // Should NOT ignore normal skill files expect(ignored.some((re) => re.test("/tmp/.hidden/skills/index.md"))).toBe(false); + expect(ignored.some((re) => re.test("/tmp/workspace/skills/my-skill/SKILL.md"))).toBe(false); }); }); diff --git a/src/agents/skills/refresh.ts b/src/agents/skills/refresh.ts index 141271ae202..8c407066345 100644 --- a/src/agents/skills/refresh.ts +++ b/src/agents/skills/refresh.ts @@ -29,6 +29,15 @@ export const DEFAULT_SKILLS_WATCH_IGNORED: RegExp[] = [ /(^|[\\/])\.git([\\/]|$)/, /(^|[\\/])node_modules([\\/]|$)/, /(^|[\\/])dist([\\/]|$)/, + // Python virtual environments and caches + /(^|[\\/])\.venv([\\/]|$)/, + /(^|[\\/])venv([\\/]|$)/, + /(^|[\\/])__pycache__([\\/]|$)/, + /(^|[\\/])\.mypy_cache([\\/]|$)/, + /(^|[\\/])\.pytest_cache([\\/]|$)/, + // Build artifacts and caches + /(^|[\\/])build([\\/]|$)/, + /(^|[\\/])\.cache([\\/]|$)/, ]; function bumpVersion(current: number): number {