fix(agent-core-v2): bound the project skill-root watch fd footprint - #2612
Open
7Sageer wants to merge 2 commits into
Open
fix(agent-core-v2): bound the project skill-root watch fd footprint#26127Sageer wants to merge 2 commits into
7Sageer wants to merge 2 commits into
Conversation
The workspace skill-root source recursively watches the skill-root candidates with chokidar, which holds one fs.watch fd per file and per directory on macOS. A skill bundling a large runtime tree can exhaust the process fd budget and break every subsequent spawn (EBADF). Mirror the scanner's own pruning (node_modules / dot entries, scan depth cap) in the watch filter, and add a signal mode to hostFsWatch: rescan-style consumers get ONE native recursive fs.watch on darwin/win32, whose fd footprint is constant in the subtree size.
🦋 Changeset detectedLatest commit: b7af1d1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
…harden signal mode Review follow-up: - The scanner probes every entry's direct SKILL.md before gating recursion, so the watch filter now keeps an excluded entry itself and its direct SKILL.md (keepEntryFile) instead of pruning them — skills under node_modules / dot directories keep their hot reload. - The signal-mode native leg now owns its recovery: a native watch error fires one root invalidation and re-arms with capped exponential backoff; chokidar is used only where recursive fs.watch is unavailable, so a transient failure can neither silently end hot reload nor downgrade to the per-node watcher. - Native event path resolution handles absolute filenames and the root-basename case, clamping out-of-root events to a root invalidation instead of dropping them. - The event mapping is extracted into NativeSignalMapper with the stat call injected, so the native-branch decisions are unit-tested on any platform.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
Resolve #2542
Problem
On macOS, 0.31.1 can exhaust the process file-descriptor budget when a skill folder contains a very large file tree (e.g. a skill bundling a Python runtime): afterwards every spawn fails with
EBADFand all tools (Bash, Grep, …) stop working. 0.29.1 did not have this problem.Root cause: the Workspace-domain refactor (#2366, first released in 0.31.1) made the workspace skill-root source recursively watch the project skill-root candidates (
.kimi-code/skills,.agents/skills, anchored at the project root so not-yet-existing roots are detected). chokidar v4 watches every node with its ownfs.watch; on macOS each watched file or directory holds one kqueue fd, and the watch filter pruned nothing inside the skill subtrees — not evennode_modulesor dot directories, which the scanner itself never descends into. A 10k-file skill tree therefore pinned 10k+ fds for the lifetime of the workspace handler (which is never closed).What changed
Two complementary fixes that keep skill hot-reload working:
subtreeWatchFiltergainsmaxDepth/skipEntryoptions, the scanner exports its exclusion rule and depth cap, and the skill-root source watches with those rules (depth cap + 2 segments for the skill directory and its SKILL.md). Subtrees the scanner would never read no longer get watched — this bounds fd and event volume on every platform.signalmode inhostFsWatch. Callers that consume events as a mere "something changed" signal (the skill source and similar rescan-style watchers) opt in; on darwin/win32 a signal-mode recursive watch then uses ONE native recursivefs.watch(FSEvents / ReadDirectoryChangesW) whose fd footprint is constant in the subtree size, instead of per-node watchers. Events in this mode may be coarse (a deleted entry of unknown kind is reported as'file'; FSEvents may coalesce a burst into an ancestor-directory event), which is safe for rescan-style consumers and documented on the interface. TheworkspaceFswatch bridge, whose subscribers receive precise action/kind, stays on chokidar unchanged.Tests: unit tests for the new filter options; signal-mode integration tests (create/modify/delete mapping, ignored predicate); a darwin-only regression test asserting the fd footprint stays bounded over a 1200-file subtree; a catalog scenario test proving pruned writes do not change the catalog while real skill edits still hot-reload. Full
agent-core-v2suite passes (296 files / 4620 tests), plustypecheckandlint:imports.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.