Skill workspace copies skip .git and node_modules
A regression that caused .git directories to be read during skill uploads has been patched. The sync operation now filters out .git and node_modules directories before copying skill trees to sandbox workspaces.
When uploading skills, the workspace sync operation was copying entire skill directory trees—including .git repositories and node_modules folders. This regression meant large binary pack files and potential secrets from .git directories were being included in the copy operation.
The fix adds a filter to the copy operation that explicitly excludes directories named .git or node_modules by basename. This matches the exclusion patterns already used by other file-walking operations throughout the skills agent code.
The change prevents read-only sandboxes from copying repo history or dependency trees, eliminating potential data exposure and reducing unnecessary I/O overhead.
View Original GitHub Description
Closes #60879
Summary
- Added a filter to the
fsp.cpcall insyncSkillsToWorkspacethat excludes.gitandnode_modulesdirectories when copying skill trees to the sandbox workspace.
Root Cause
syncSkillsToWorkspace in src/agents/skills/workspace.ts uses fsp.cp with recursive: true but no filter option, so the entire skill directory tree is copied verbatim, including .git (which can contain large binary pack files) and node_modules.
Other file-walking code in the repo (e.g. computeSkillFingerprint, walkDirWithLimit in the skill scanner, listCandidateSkillDirs) already skips dot-directories and node_modules, so this was the one path that was missing the exclusion.
Changes
src/agents/skills/workspace.ts - added a filter callback to fsp.cp that rejects entries named .git or node_modules.
Test Plan
-
npx vitest run src/agents/skills/- 34/34 pass - No lint errors on touched files
Risks and Mitigations
- The filter is conservative: it only skips
.gitandnode_modulesby basename, matching the patterns used by the rest of the skill file-walking code. - No user-facing skill data lives in
.gitornode_modules, so excluding them has no functional impact.