From f0c970fb43c27232b06fc6a5cdd00e994310486b Mon Sep 17 00:00:00 2001 From: OfflynAI <140015627+joelnishanth@users.noreply.github.com> Date: Sat, 4 Apr 2026 22:12:58 -0500 Subject: [PATCH] fix: skip sandbox skill copy junk (#61090) (thanks @joelnishanth) * fix(skills): exclude .git and node_modules when copying skills to workspace (#60879) * fix(skills): cover sync copy exclusions * fix: skip sandbox skill copy junk (#61090) (thanks @joelnishanth) --------- Co-authored-by: Ayaan Zaidi --- CHANGELOG.md | 1 + ...cs-merged-skills-into-target-workspace.test.ts | 15 +++++++++++++++ src/agents/skills/workspace.ts | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b40e90b035..8b899482513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ Docs: https://docs.openclaw.ai - CLI/Commander: preserve Commander-computed exit codes for argument and help-error paths, and cover the user-argv parse mode in the regression tests so invalid CLI invocations no longer report success when exits are intercepted. (#60923) Thanks @Linux2010. - Telegram/native command menu: trim long menu descriptions before dropping commands so sub-100 command sets can still fit Telegram's payload budget and keep more `/` entries visible. (#61129) Thanks @neeravmakwana. - Agents/Claude CLI: keep non-interactive `--permission-mode bypassPermissions` when custom `cliBackends.claude-cli.args` override defaults, so cron and heartbeat Claude CLI runs do not regress to interactive approval mode. (#61114) Thanks @cathrynlavery and @thewilloftheshadow. +- Agents/skills: skip `.git` and `node_modules` when mirroring skills into sandbox workspaces so read-only sandboxes do not copy repo history or dependency trees. (#61090) Thanks @joelnishanth. ## 2026.4.2 diff --git a/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts b/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts index c74559bdb20..5b9f87a9241 100644 --- a/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts +++ b/src/agents/skills.build-workspace-skills-prompt.syncs-merged-skills-into-target-workspace.test.ts @@ -100,6 +100,15 @@ describe("buildWorkspaceSkillsPrompt", () => { const extraDir = path.join(sourceWorkspace, ".extra"); const bundledDir = path.join(sourceWorkspace, ".bundled"); const managedDir = path.join(sourceWorkspace, ".managed"); + const workspaceSkillDir = path.join(sourceWorkspace, "skills", "demo-skill"); + + await fs.mkdir(path.join(workspaceSkillDir, ".git"), { recursive: true }); + await fs.writeFile(path.join(workspaceSkillDir, ".git", "config"), "gitdir"); + await fs.mkdir(path.join(workspaceSkillDir, "node_modules", "pkg"), { recursive: true }); + await fs.writeFile( + path.join(workspaceSkillDir, "node_modules", "pkg", "index.js"), + "export {}", + ); await withEnv({ HOME: sourceWorkspace, PATH: "" }, () => syncSkillsToWorkspace({ @@ -121,6 +130,12 @@ describe("buildWorkspaceSkillsPrompt", () => { expect(prompt).not.toContain("Bundled version"); expect(prompt).not.toContain("Extra version"); expect(prompt.replaceAll("\\", "/")).toContain("demo-skill/SKILL.md"); + expect(await pathExists(path.join(targetWorkspace, "skills", "demo-skill", ".git"))).toBe( + false, + ); + expect( + await pathExists(path.join(targetWorkspace, "skills", "demo-skill", "node_modules")), + ).toBe(false); }); it("syncs the explicit agent skill subset instead of inherited defaults", async () => { diff --git a/src/agents/skills/workspace.ts b/src/agents/skills/workspace.ts index 57ea27ba466..2b51a6d80b1 100644 --- a/src/agents/skills/workspace.ts +++ b/src/agents/skills/workspace.ts @@ -825,6 +825,10 @@ export async function syncSkillsToWorkspace(params: { await fsp.cp(entry.skill.baseDir, dest, { recursive: true, force: true, + filter: (src) => { + const name = path.basename(src); + return !(name === ".git" || name === "node_modules"); + }, }); } catch (error) { const message = error instanceof Error ? error.message : JSON.stringify(error);