From 51ac9be8cf4c297fd730412140382909e0898954 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 14:13:57 +0000 Subject: [PATCH 1/9] Initial plan From 5e23ab661173a910c07d8dd3ef65c4cd72375b3b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 14:16:10 +0000 Subject: [PATCH 2/9] Restrict sandbox device mounts --- packages/runtime/src/band-server.ts | 5 +++-- packages/runtime/src/banded-skills/lima-exec-utils.ts | 5 +++-- .../test/unit/banded-skills/lima-exec-utils.test.ts | 9 ++++++--- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/runtime/src/band-server.ts b/packages/runtime/src/band-server.ts index 211c568..0dc58e9 100644 --- a/packages/runtime/src/band-server.ts +++ b/packages/runtime/src/band-server.ts @@ -240,8 +240,9 @@ function buildBwrapArgs( "--symlink", "usr/lib64", "/lib64", "--ro-bind", "/etc", "/etc", "--bind-try", "/run", "/run", - "--proc", "/proc", - "--dev", "/dev", + "--dev-bind", "/dev/null", "/dev/null", + "--dev-bind", "/dev/zero", "/dev/zero", + "--dev-bind", "/dev/urandom", "/dev/urandom", "--perms", "1777", "--tmpfs", "/tmp", "--perms", "1777", "--tmpfs", "/home", "--bind", workdir, workdir, diff --git a/packages/runtime/src/banded-skills/lima-exec-utils.ts b/packages/runtime/src/banded-skills/lima-exec-utils.ts index d1c5d36..67e3fe2 100644 --- a/packages/runtime/src/banded-skills/lima-exec-utils.ts +++ b/packages/runtime/src/banded-skills/lima-exec-utils.ts @@ -28,8 +28,9 @@ export function buildBwrapCommand( "--ro-bind-try /etc/ssl /etc/ssl", "--ro-bind-try /etc/ca-certificates /etc/ca-certificates", "--ro-bind-try /etc/alternatives /etc/alternatives", - "--proc /proc", - "--dev /dev", + "--dev-bind /dev/null /dev/null", + "--dev-bind /dev/zero /dev/zero", + "--dev-bind /dev/urandom /dev/urandom", "--tmpfs /tmp", "--tmpfs /home", `--bind ${vmWorkdir} ${vmWorkdir}`, diff --git a/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts b/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts index ebc5ec1..8b2c8c5 100644 --- a/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts +++ b/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts @@ -22,10 +22,13 @@ describe("buildBwrapCommand", () => { expect(cmd).toContain("--ro-bind-try /etc/ca-certificates /etc/ca-certificates"); }); - test("includes proc, dev, tmpfs mounts", () => { + test("restricts proc and dev mounts", () => { const cmd = buildBwrapCommand("/work/abc"); - expect(cmd).toContain("--proc /proc"); - expect(cmd).toContain("--dev /dev"); + expect(cmd).not.toContain("--proc /proc"); + expect(cmd).not.toContain("--dev /dev"); + expect(cmd).toContain("--dev-bind /dev/null /dev/null"); + expect(cmd).toContain("--dev-bind /dev/zero /dev/zero"); + expect(cmd).toContain("--dev-bind /dev/urandom /dev/urandom"); expect(cmd).toContain("--tmpfs /tmp"); expect(cmd).toContain("--tmpfs /home"); }); From 0311cb014ecc477046b571f386f36543783550ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 May 2026 16:46:25 +0000 Subject: [PATCH 3/9] Create sandbox dev directory --- packages/runtime/src/band-server.ts | 1 + packages/runtime/src/banded-skills/lima-exec-utils.ts | 1 + packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/runtime/src/band-server.ts b/packages/runtime/src/band-server.ts index 0dc58e9..db31dd9 100644 --- a/packages/runtime/src/band-server.ts +++ b/packages/runtime/src/band-server.ts @@ -240,6 +240,7 @@ function buildBwrapArgs( "--symlink", "usr/lib64", "/lib64", "--ro-bind", "/etc", "/etc", "--bind-try", "/run", "/run", + "--dir", "/dev", "--dev-bind", "/dev/null", "/dev/null", "--dev-bind", "/dev/zero", "/dev/zero", "--dev-bind", "/dev/urandom", "/dev/urandom", diff --git a/packages/runtime/src/banded-skills/lima-exec-utils.ts b/packages/runtime/src/banded-skills/lima-exec-utils.ts index 67e3fe2..e3d9378 100644 --- a/packages/runtime/src/banded-skills/lima-exec-utils.ts +++ b/packages/runtime/src/banded-skills/lima-exec-utils.ts @@ -28,6 +28,7 @@ export function buildBwrapCommand( "--ro-bind-try /etc/ssl /etc/ssl", "--ro-bind-try /etc/ca-certificates /etc/ca-certificates", "--ro-bind-try /etc/alternatives /etc/alternatives", + "--dir /dev", "--dev-bind /dev/null /dev/null", "--dev-bind /dev/zero /dev/zero", "--dev-bind /dev/urandom /dev/urandom", diff --git a/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts b/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts index 8b2c8c5..47425d3 100644 --- a/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts +++ b/packages/runtime/test/unit/banded-skills/lima-exec-utils.test.ts @@ -26,6 +26,7 @@ describe("buildBwrapCommand", () => { const cmd = buildBwrapCommand("/work/abc"); expect(cmd).not.toContain("--proc /proc"); expect(cmd).not.toContain("--dev /dev"); + expect(cmd).toContain("--dir /dev"); expect(cmd).toContain("--dev-bind /dev/null /dev/null"); expect(cmd).toContain("--dev-bind /dev/zero /dev/zero"); expect(cmd).toContain("--dev-bind /dev/urandom /dev/urandom"); From 440263491d25ae472fa058b2c10dbd59d50c5ce5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:43:15 +0000 Subject: [PATCH 4/9] Stabilize CI skill tests --- .github/workflows/ci.yml | 2 +- .../github/test/github-skill-issue-edit.test.ts | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7766a8f..8780f52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,7 +150,7 @@ jobs: skill-tests-agent: name: Skill Tests (Agent) runs-on: ubuntu-latest - needs: unit-tests + needs: skill-tests-direct steps: - uses: actions/checkout@v4 diff --git a/skills/github/test/github-skill-issue-edit.test.ts b/skills/github/test/github-skill-issue-edit.test.ts index 57c904c..8d7db35 100644 --- a/skills/github/test/github-skill-issue-edit.test.ts +++ b/skills/github/test/github-skill-issue-edit.test.ts @@ -5,6 +5,10 @@ import { describe, expect, test, beforeAll } from "bun:test"; import { gh, GITHUB_REPO, TIMEOUT } from "./github-helpers"; +async function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + describe("github: issue edit & close/reopen", () => { // ── Labels + assignees ────────────────────────────────────────── @@ -37,9 +41,14 @@ describe("github: issue edit & close/reopen", () => { test("view issue shows label and assignee", async () => { expect(issueNumber).toBeDefined(); - const result = await gh("issue-view", { repo: GITHUB_REPO!, number: issueNumber }); - if (!result.success) throw new Error(`issue-view labels failed: ${result.error}`); - const data = result.data as any; + let data: any; + for (let attempt = 0; attempt < 5; attempt++) { + const result = await gh("issue-view", { repo: GITHUB_REPO!, number: issueNumber }); + if (!result.success) throw new Error(`issue-view labels failed: ${result.error}`); + data = result.data as any; + if (data.labels.some((l: any) => l.name === labelName) && data.assignees.length >= 1) break; + if (attempt < 4) await sleep(1500); + } expect(data.labels.some((l: any) => l.name === labelName)).toBe(true); expect(data.assignees.length).toBeGreaterThanOrEqual(1); }, TIMEOUT); From 24c8bffc15768bc4f506be21014de08a6673892a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:44:46 +0000 Subject: [PATCH 5/9] Refine CI retry test --- .../test/github-skill-issue-edit.test.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/skills/github/test/github-skill-issue-edit.test.ts b/skills/github/test/github-skill-issue-edit.test.ts index 8d7db35..30b102f 100644 --- a/skills/github/test/github-skill-issue-edit.test.ts +++ b/skills/github/test/github-skill-issue-edit.test.ts @@ -5,6 +5,9 @@ import { describe, expect, test, beforeAll } from "bun:test"; import { gh, GITHUB_REPO, TIMEOUT } from "./github-helpers"; +const MAX_VISIBILITY_ATTEMPTS = 5; +const VISIBILITY_RETRY_DELAY_MS = 1500; + async function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -41,16 +44,17 @@ describe("github: issue edit & close/reopen", () => { test("view issue shows label and assignee", async () => { expect(issueNumber).toBeDefined(); - let data: any; - for (let attempt = 0; attempt < 5; attempt++) { + let issueData: { labels: Array<{ name: string }>; assignees: unknown[] } | undefined; + for (let attempt = 0; attempt < MAX_VISIBILITY_ATTEMPTS; attempt++) { const result = await gh("issue-view", { repo: GITHUB_REPO!, number: issueNumber }); if (!result.success) throw new Error(`issue-view labels failed: ${result.error}`); - data = result.data as any; - if (data.labels.some((l: any) => l.name === labelName) && data.assignees.length >= 1) break; - if (attempt < 4) await sleep(1500); + issueData = result.data as { labels: Array<{ name: string }>; assignees: unknown[] }; + if (issueData.labels.some((l) => l.name === labelName) && issueData.assignees.length >= 1) break; + if (attempt < MAX_VISIBILITY_ATTEMPTS - 1) await sleep(VISIBILITY_RETRY_DELAY_MS); } - expect(data.labels.some((l: any) => l.name === labelName)).toBe(true); - expect(data.assignees.length).toBeGreaterThanOrEqual(1); + if (!issueData) throw new Error("issue-view did not return data"); + expect(issueData.labels.some((l) => l.name === labelName)).toBe(true); + expect(issueData.assignees.length).toBeGreaterThanOrEqual(1); }, TIMEOUT); test("list issues filtered by assignee", async () => { From aadfc8df6107a67ee245af339430f8d7af230088 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:46:25 +0000 Subject: [PATCH 6/9] Retry transient PR merge failures --- .github/workflows/ci.yml | 2 +- .../github/scripts/resources/pr-merge/run.sh | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8780f52..7766a8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,7 +150,7 @@ jobs: skill-tests-agent: name: Skill Tests (Agent) runs-on: ubuntu-latest - needs: skill-tests-direct + needs: unit-tests steps: - uses: actions/checkout@v4 diff --git a/skills/github/scripts/resources/pr-merge/run.sh b/skills/github/scripts/resources/pr-merge/run.sh index 0cffa06..5e99d07 100755 --- a/skills/github/scripts/resources/pr-merge/run.sh +++ b/skills/github/scripts/resources/pr-merge/run.sh @@ -20,12 +20,22 @@ fi # gh pr merge outputs text, not JSON STDERR_FILE=$(mktemp) -RESULT=$(gh pr merge "${ARGS[@]}" 2>"$STDERR_FILE") || { +MAX_ATTEMPTS=3 +for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do + RESULT=$(gh pr merge "${ARGS[@]}" 2>"$STDERR_FILE") && break ERROR=$(cat "$STDERR_FILE") + if [[ "$ERROR" != *"Base branch was modified"* || "$attempt" -eq "$MAX_ATTEMPTS" ]]; then + rm -f "$STDERR_FILE" + echo "{\"error\": \"$ERROR\"}" > "${OUTPUT_PATH:-/dev/stdout}" + exit 1 + fi + : > "$STDERR_FILE" + sleep 2 +done +if [ -s "$STDERR_FILE" ]; then rm -f "$STDERR_FILE" - echo "{\"error\": \"$ERROR\"}" > "${OUTPUT_PATH:-/dev/stdout}" - exit 1 -} -rm -f "$STDERR_FILE" +else + rm -f "$STDERR_FILE" +fi echo "{\"merged\": true, \"message\": \"$RESULT\"}" > "${OUTPUT_PATH:-/dev/stdout}" From 9a713b053d18f9b7bd2d2f6025b1a4ec9edbd5dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:47:29 +0000 Subject: [PATCH 7/9] Clean up PR merge retry --- skills/github/scripts/resources/pr-merge/run.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/skills/github/scripts/resources/pr-merge/run.sh b/skills/github/scripts/resources/pr-merge/run.sh index 5e99d07..082f55f 100755 --- a/skills/github/scripts/resources/pr-merge/run.sh +++ b/skills/github/scripts/resources/pr-merge/run.sh @@ -32,10 +32,6 @@ for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do : > "$STDERR_FILE" sleep 2 done -if [ -s "$STDERR_FILE" ]; then - rm -f "$STDERR_FILE" -else - rm -f "$STDERR_FILE" -fi +rm -f "$STDERR_FILE" echo "{\"merged\": true, \"message\": \"$RESULT\"}" > "${OUTPUT_PATH:-/dev/stdout}" From 6c5ab545b1abc05417b3d708e865dbe0e09bf830 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:48:09 +0000 Subject: [PATCH 8/9] Clarify CI retry helpers --- skills/github/scripts/resources/pr-merge/run.sh | 1 + skills/github/test/github-skill-issue-edit.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/skills/github/scripts/resources/pr-merge/run.sh b/skills/github/scripts/resources/pr-merge/run.sh index 082f55f..c75b77a 100755 --- a/skills/github/scripts/resources/pr-merge/run.sh +++ b/skills/github/scripts/resources/pr-merge/run.sh @@ -29,6 +29,7 @@ for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do echo "{\"error\": \"$ERROR\"}" > "${OUTPUT_PATH:-/dev/stdout}" exit 1 fi + # Clear the previous error before retrying so only the next attempt is inspected. : > "$STDERR_FILE" sleep 2 done diff --git a/skills/github/test/github-skill-issue-edit.test.ts b/skills/github/test/github-skill-issue-edit.test.ts index 30b102f..7872413 100644 --- a/skills/github/test/github-skill-issue-edit.test.ts +++ b/skills/github/test/github-skill-issue-edit.test.ts @@ -6,7 +6,7 @@ import { describe, expect, test, beforeAll } from "bun:test"; import { gh, GITHUB_REPO, TIMEOUT } from "./github-helpers"; const MAX_VISIBILITY_ATTEMPTS = 5; -const VISIBILITY_RETRY_DELAY_MS = 1500; +const RETRY_DELAY_MS = 1500; async function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -50,7 +50,7 @@ describe("github: issue edit & close/reopen", () => { if (!result.success) throw new Error(`issue-view labels failed: ${result.error}`); issueData = result.data as { labels: Array<{ name: string }>; assignees: unknown[] }; if (issueData.labels.some((l) => l.name === labelName) && issueData.assignees.length >= 1) break; - if (attempt < MAX_VISIBILITY_ATTEMPTS - 1) await sleep(VISIBILITY_RETRY_DELAY_MS); + if (attempt < MAX_VISIBILITY_ATTEMPTS - 1) await sleep(RETRY_DELAY_MS); } if (!issueData) throw new Error("issue-view did not return data"); expect(issueData.labels.some((l) => l.name === labelName)).toBe(true); From 3c272d75623157de46a6ebda255e5468948e73a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 17 May 2026 00:49:17 +0000 Subject: [PATCH 9/9] Improve retry readability --- skills/github/scripts/resources/pr-merge/run.sh | 2 +- skills/github/test/github-skill-issue-edit.test.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/skills/github/scripts/resources/pr-merge/run.sh b/skills/github/scripts/resources/pr-merge/run.sh index c75b77a..11cba17 100755 --- a/skills/github/scripts/resources/pr-merge/run.sh +++ b/skills/github/scripts/resources/pr-merge/run.sh @@ -30,7 +30,7 @@ for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do exit 1 fi # Clear the previous error before retrying so only the next attempt is inspected. - : > "$STDERR_FILE" + truncate -s 0 "$STDERR_FILE" sleep 2 done rm -f "$STDERR_FILE" diff --git a/skills/github/test/github-skill-issue-edit.test.ts b/skills/github/test/github-skill-issue-edit.test.ts index 7872413..651b5d6 100644 --- a/skills/github/test/github-skill-issue-edit.test.ts +++ b/skills/github/test/github-skill-issue-edit.test.ts @@ -44,17 +44,17 @@ describe("github: issue edit & close/reopen", () => { test("view issue shows label and assignee", async () => { expect(issueNumber).toBeDefined(); - let issueData: { labels: Array<{ name: string }>; assignees: unknown[] } | undefined; + let latestIssueData: { labels: Array<{ name: string }>; assignees: unknown[] } | undefined; for (let attempt = 0; attempt < MAX_VISIBILITY_ATTEMPTS; attempt++) { const result = await gh("issue-view", { repo: GITHUB_REPO!, number: issueNumber }); if (!result.success) throw new Error(`issue-view labels failed: ${result.error}`); - issueData = result.data as { labels: Array<{ name: string }>; assignees: unknown[] }; - if (issueData.labels.some((l) => l.name === labelName) && issueData.assignees.length >= 1) break; + latestIssueData = result.data as { labels: Array<{ name: string }>; assignees: unknown[] }; + if (latestIssueData.labels.some((l) => l.name === labelName) && latestIssueData.assignees.length >= 1) break; if (attempt < MAX_VISIBILITY_ATTEMPTS - 1) await sleep(RETRY_DELAY_MS); } - if (!issueData) throw new Error("issue-view did not return data"); - expect(issueData.labels.some((l) => l.name === labelName)).toBe(true); - expect(issueData.assignees.length).toBeGreaterThanOrEqual(1); + if (!latestIssueData) throw new Error("issue-view did not return data"); + expect(latestIssueData.labels.some((l) => l.name === labelName)).toBe(true); + expect(latestIssueData.assignees.length).toBeGreaterThanOrEqual(1); }, TIMEOUT); test("list issues filtered by assignee", async () => {