From e2b8595b1122198f9387afd5d21203966ae86a15 Mon Sep 17 00:00:00 2001 From: Luca Giordano Date: Mon, 6 Jul 2026 17:46:50 +0200 Subject: [PATCH] Wire SANDCASTLE_CONTAINER_UID/GID through to SandboxRunner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sandbox-runner.ts already validates a built image's UID/GID against RunnerOptions.containerUid/containerGid (default 1000), but main.ts never set them from env — only SANDCASTLE_IMAGE was wired. Any custom base image with a non-1000 non-root user (e.g. the dotnet SDK base's uid 1654) hit a hard UID-mismatch failure with no way to override it short of editing main.ts directly. Discovered via the live dotnet-recipe validation run (issue #04): all 3 retries failed with the same WorktreeError before this fix. --- .sandcastle/main.ts | 12 ++++++++++++ examples/dotnet-project/orchestrator.env | 6 ++++++ 2 files changed, 18 insertions(+) diff --git a/.sandcastle/main.ts b/.sandcastle/main.ts index 124e09d..9e1e202 100644 --- a/.sandcastle/main.ts +++ b/.sandcastle/main.ts @@ -14,6 +14,10 @@ * AGENTIC_BASE_BRANCH PR base branch (default: main) * AGENTIC_MODEL claudeCode model (default: claude-sonnet-4-6) * SANDCASTLE_IMAGE inner image (default: sandcastle:local) + * SANDCASTLE_CONTAINER_UID inner image's non-root UID (default: 1000, sandcastle's + * own default — only needed when a custom image renames its + * user to a different UID, e.g. the dotnet SDK base's 1654) + * SANDCASTLE_CONTAINER_GID matching GID (default: 1000) * AGENTIC_CONCURRENCY max parallel sandboxes (default: 1, serial) * SMEE_URL smee.io channel URL (enables event-driven mode) * WEBHOOK_SECRET shared HMAC secret for X-Hub-Signature-256 validation @@ -603,6 +607,12 @@ async function main(): Promise { const issues = new IssueSource(repo); const tier = (process.env.AGENTIC_TIER ?? "claude") as "claude" | "local"; + const containerUid = process.env.SANDCASTLE_CONTAINER_UID + ? Number(process.env.SANDCASTLE_CONTAINER_UID) + : undefined; + const containerGid = process.env.SANDCASTLE_CONTAINER_GID + ? Number(process.env.SANDCASTLE_CONTAINER_GID) + : undefined; const runner = new SandboxRunner({ imageName: process.env.SANDCASTLE_IMAGE, model: process.env.AGENTIC_MODEL, @@ -611,6 +621,8 @@ async function main(): Promise { localImageName: process.env.SANDCASTLE_OPENCODE_IMAGE, cwd: repoRoot, network, + containerUid, + containerGid, }); const reviewer = new ReviewerAdapter({ diff --git a/examples/dotnet-project/orchestrator.env b/examples/dotnet-project/orchestrator.env index ab60b10..6245c37 100644 --- a/examples/dotnet-project/orchestrator.env +++ b/examples/dotnet-project/orchestrator.env @@ -10,6 +10,12 @@ GH_TOKEN= # docker build -f .sandcastle/Dockerfile.dotnet -t sandcastle-dotnet:local .sandcastle SANDCASTLE_IMAGE=sandcastle-dotnet:local +# mcr.microsoft.com/dotnet/sdk:8.0 ships its own non-root user at uid/gid 1654 +# (not sandcastle's default of 1000) — Dockerfile.dotnet renames it but keeps +# the id, so the runner must be told to expect 1654 instead of assuming 1000. +SANDCASTLE_CONTAINER_UID=1654 +SANDCASTLE_CONTAINER_GID=1654 + # Target repo is NOT the one `gh` detects from the working directory here — # this recipe validates against a small, purpose-built dotnet fixture repo, # so AGENTIC_REPO is set explicitly rather than left to the default.