From cb47e89aa8e541f16cedfcdb870d3fe81ec13d62 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Tue, 14 Apr 2026 07:29:11 -0700 Subject: [PATCH] fix(workspace): recursive chown when /workspace bind mount is root-owned (#13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Docker Desktop (macOS/Windows), host-path bind mounts often appear root-owned inside the container. The previous entrypoint only chowned /workspace top-level, so agents (uid 1000) still couldn't write to /workspace/repo/* — git clone, pip install, and file edits failed with EACCES and fell back to /tmp. Detect the root-owned-contents case by sampling the first entry; if it's root-owned, recursively chown the tree. On normal Linux Docker with matching uids this is a no-op, so the fast-startup path is preserved for the common case. Part B of the issue (private-repo initial_prompt clone) was addressed by PR #20. Co-Authored-By: Claude Opus 4.6 (1M context) --- workspace-template/entrypoint.sh | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/workspace-template/entrypoint.sh b/workspace-template/entrypoint.sh index 2dc40dea..b00ccb3e 100644 --- a/workspace-template/entrypoint.sh +++ b/workspace-template/entrypoint.sh @@ -11,10 +11,24 @@ if [ "$(id -u)" = "0" ]; then # Fix /configs recursively (plugins, CLAUDE.md, skills — small directory) chown -R agent:agent /configs 2>/dev/null - # Fix /workspace top-level only — it may be a bind-mounted host repo with - # thousands of files. Recursive chown would take minutes and change the - # host filesystem's ownership. The agent only needs to write at the top level. + # /workspace handling: + # - Always fix the top-level dir so agent can create files in it. + # - If the contents are root-owned (common on Docker Desktop / Windows + # bind mounts where host uid maps to 0 inside the container), do a + # full recursive chown — otherwise git clone, pip install, and file + # writes under /workspace fail with EACCES (issue #13). On normal + # Linux Docker with matching uids this branch is skipped, so we keep + # the fast startup for the common case. chown agent:agent /workspace 2>/dev/null + if [ -d /workspace ]; then + # Sample the first entry inside /workspace; if it's root-owned assume + # the whole tree is a root-owned bind mount and recursively chown. + first_entry=$(find /workspace -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null) + if [ -n "$first_entry" ] && [ "$(stat -c '%u' "$first_entry" 2>/dev/null)" = "0" ]; then + echo "[entrypoint] /workspace contents are root-owned — chowning recursively to agent (uid 1000)" + chown -R agent:agent /workspace 2>/dev/null + fi + fi # Re-exec this script as the agent user via gosu (clean PID 1 handoff) exec gosu agent "$0" "$@" fi