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 <hi@obviy.us>
This commit is contained in:
OfflynAI
2026-04-04 22:12:58 -05:00
committed by GitHub
parent a235f5ed64
commit f0c970fb43
3 changed files with 20 additions and 0 deletions

View File

@@ -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

View File

@@ -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 () => {

View File

@@ -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);