Skip to content

Commit a932c5e

Browse files
barckcodeclaude
andcommitted
fix: make workspace writable for non-root agent containers
The API process runs as root and creates workspace files (CLAUDE.md, sub-agent configs) with root:root ownership and 0755/0644 permissions. When the agent container starts as the non-root agentcrew user (uid 999), it cannot write to the bind-mounted workspace, causing "permission denied" errors for .claude dir creation and MCP config writes. Add makeWorkspaceWritable() that recursively sets 0777/0666 permissions on the workspace directory before mounting it into the agent container. This ensures the container user can write regardless of UID mismatch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4e98ca7 commit a932c5e

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.6
1+
0.2.7

internal/runtime/docker.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,19 @@ func (d *DockerRuntime) DeployAgent(ctx context.Context, config AgentConfig) (*A
479479
resources.NanoCPUs = parseCPULimit(config.Resources.CPU)
480480
}
481481

482+
// Ensure the workspace directory (and all files written by the API) is
483+
// writable by the non-root agent user inside the container. The API runs
484+
// as root but the agent container runs as uid 999 (agentcrew), so any
485+
// files created on the host via SetupAgentWorkspace / SetupSubAgentFile
486+
// would be owned by root with 0755/0644 permissions — unwritable by the
487+
// container user. We fix this by making the entire workspace
488+
// world-writable before mounting it.
489+
if config.WorkspacePath != "" {
490+
if err := makeWorkspaceWritable(config.WorkspacePath); err != nil {
491+
slog.Warn("failed to make workspace writable", "path", config.WorkspacePath, "error", err)
492+
}
493+
}
494+
482495
// Determine workspace bind: use host path (bind mount) if provided,
483496
// otherwise fall back to the shared Docker volume.
484497
binds := []string{}
@@ -702,6 +715,22 @@ func (d *DockerRuntime) WriteFile(ctx context.Context, containerID string, path
702715
return nil
703716
}
704717

718+
// makeWorkspaceWritable recursively sets permissions on the workspace directory
719+
// so that the non-root agent user (uid 999) inside the container can write to it.
720+
// The API creates workspace files as root, but the agent container runs as
721+
// agentcrew:999 — without this step, the sidecar gets "permission denied" errors.
722+
func makeWorkspaceWritable(workspacePath string) error {
723+
return filepath.Walk(workspacePath, func(path string, info os.FileInfo, err error) error {
724+
if err != nil {
725+
return err
726+
}
727+
if info.IsDir() {
728+
return os.Chmod(path, 0777)
729+
}
730+
return os.Chmod(path, 0666)
731+
})
732+
}
733+
705734
// parseMemoryLimit converts a human-readable memory string (e.g. "512m", "1g")
706735
// to bytes. Returns 0 if parsing fails.
707736
func parseMemoryLimit(mem string) int64 {

0 commit comments

Comments
 (0)