From 86e63b55ee4afed97721c6121e9006b7c98ca3ad Mon Sep 17 00:00:00 2001 From: Yuqing Zhai Date: Thu, 7 May 2026 10:56:17 -0500 Subject: [PATCH] fix(workbench): unblock Linux Docker bind-mount permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The agent container runs non-root and writes results into a host-mounted results directory. On Linux the bind-mount inherits the host owner, so the container couldn't write `result.json`. Fix: - chmod 0777 on workDir + resultsDir before starting the container - chmod -R a+rw on resultsDir after `docker cp` so cleanup can read it Also gitignore .superpowers/ — runtime state from the categorization pipeline (per-skill JSON cache, progress logs). --- .gitignore | 1 + src/workbench/docker-runner.ts | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 955a362..087e5ce 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ temp/ docs/superpowers/ docs/plans/ docs/specs/ +.superpowers/ # Skill-optimizer generated artifacts .skill-optimizer/ diff --git a/src/workbench/docker-runner.ts b/src/workbench/docker-runner.ts index 292ce6a..b0f5451 100644 --- a/src/workbench/docker-runner.ts +++ b/src/workbench/docker-runner.ts @@ -1,4 +1,4 @@ -import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { chmodSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -212,6 +212,8 @@ async function copyAgentResults(containerName: string, resultsDir: string, repoR copy.stderr.trim(), ].filter(Boolean).join('\n\n')); } + + await runShellCommand(`chmod -R a+rw ${shellQuote(resultsDir)}`, { cwd: repoRoot }); } async function removeContainer(containerName: string, repoRoot: string): Promise { @@ -408,6 +410,8 @@ export function prepareDockerWorkbenchRun( mkdirSync(referencesDir, { recursive: true }); mkdirSync(workDir, { recursive: true }); mkdirSync(resultsDir, { recursive: true }); + chmodSync(workDir, 0o777); + chmodSync(resultsDir, 0o777); copyDirectoryContents(resolvedCase.referencesDir, referencesDir); copyCaseSupportDirs(resolvedCase.configDir, caseDir); @@ -434,7 +438,15 @@ export function prepareDockerWorkbenchRun( resultPath: join(resultsDir, 'result.json'), tracePath: join(resultsDir, 'trace.jsonl'), ...(mcpConfigPath ? { mcpConfigPath } : {}), - cleanup: () => rmSync(tempDir, { recursive: true, force: true }), + cleanup: () => { + try { + rmSync(tempDir, { recursive: true, force: true }); + } catch (error) { + // The container (uid 10001) may write subdirs (.cache, .venv) that the host user + // cannot delete. Don't let cleanup failures kill the run; tmpfiles.d will sweep /tmp later. + console.warn(`workbench: could not remove ${tempDir}: ${error instanceof Error ? error.message : String(error)}`); + } + }, }; }