diff --git a/packages/runtime/src/band-server.ts b/packages/runtime/src/band-server.ts index ae55442..c06746a 100644 --- a/packages/runtime/src/band-server.ts +++ b/packages/runtime/src/band-server.ts @@ -251,8 +251,10 @@ function buildBwrapArgs( "--symlink", "usr/lib64", "/lib64", "--ro-bind", "/etc", "/etc", "--bind-try", "/run", "/run", - "--proc", "/proc", - "--dev", "/dev", + "--dir", "/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..e3d9378 100644 --- a/packages/runtime/src/banded-skills/lima-exec-utils.ts +++ b/packages/runtime/src/banded-skills/lima-exec-utils.ts @@ -28,8 +28,10 @@ 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", + "--dir /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..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 @@ -22,10 +22,14 @@ 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("--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"); expect(cmd).toContain("--tmpfs /tmp"); expect(cmd).toContain("--tmpfs /home"); }); diff --git a/skills/github/scripts/resources/pr-merge/run.sh b/skills/github/scripts/resources/pr-merge/run.sh index 0cffa06..11cba17 100755 --- a/skills/github/scripts/resources/pr-merge/run.sh +++ b/skills/github/scripts/resources/pr-merge/run.sh @@ -20,12 +20,19 @@ 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") - rm -f "$STDERR_FILE" - echo "{\"error\": \"$ERROR\"}" > "${OUTPUT_PATH:-/dev/stdout}" - exit 1 -} + 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 + # Clear the previous error before retrying so only the next attempt is inspected. + truncate -s 0 "$STDERR_FILE" + sleep 2 +done rm -f "$STDERR_FILE" echo "{\"merged\": true, \"message\": \"$RESULT\"}" > "${OUTPUT_PATH:-/dev/stdout}" diff --git a/skills/github/test/github-skill-issue-edit.test.ts b/skills/github/test/github-skill-issue-edit.test.ts index b0f7c29..3b4386f 100644 --- a/skills/github/test/github-skill-issue-edit.test.ts +++ b/skills/github/test/github-skill-issue-edit.test.ts @@ -5,6 +5,13 @@ import { describe, expect, test, beforeAll } from "bun:test"; import { gh, GITHUB_REPO, TIMEOUT, pollIssueState } from "./github-helpers"; +const MAX_VISIBILITY_ATTEMPTS = 5; +const RETRY_DELAY_MS = 1500; + +async function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + describe("github: issue edit & close/reopen", () => { // ── Labels + assignees ────────────────────────────────────────── @@ -37,11 +44,17 @@ 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; - expect(data.labels.some((l: any) => l.name === labelName)).toBe(true); - expect(data.assignees.length).toBeGreaterThanOrEqual(1); + 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}`); + 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 (!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 () => {