diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml index d9fb8a1d..df537ab2 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/pr-test.yml @@ -889,7 +889,10 @@ jobs: env: YAO_SANDBOX_WORKSPACE: ${{ runner.temp }}/sandbox/workspace YAO_SANDBOX_IPC: ${{ runner.temp }}/sandbox/ipc - run: make unit-test-sandbox + run: | + # Use runner's UID:GID to avoid permission issues + export YAO_SANDBOX_CONTAINER_USER="$(id -u):$(id -g)" + make unit-test-sandbox - name: Codecov Report uses: codecov/codecov-action@v4 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 9d36cd18..79a1f5b7 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -659,7 +659,10 @@ jobs: env: YAO_SANDBOX_WORKSPACE: ${{ runner.temp }}/sandbox/workspace YAO_SANDBOX_IPC: ${{ runner.temp }}/sandbox/ipc - run: make unit-test-sandbox + run: | + # Use runner's UID:GID to match host permissions + export YAO_SANDBOX_CONTAINER_USER="$(id -u):$(id -g)" + make unit-test-sandbox - name: Codecov Report uses: codecov/codecov-action@v4 diff --git a/sandbox/config.go b/sandbox/config.go index b73b5542..f6335c5e 100644 --- a/sandbox/config.go +++ b/sandbox/config.go @@ -20,6 +20,7 @@ type Config struct { // Container internal paths ContainerWorkDir string `json:"container_workdir,omitempty"` // Container working directory, default: /workspace ContainerIPCSocket string `json:"container_ipc_socket,omitempty"` // Container IPC socket path, default: /tmp/yao.sock + ContainerUser string `json:"container_user,omitempty"` // Container user, default: "" (use image default). Set to "0" for root. } // DefaultConfig returns a Config with default values @@ -110,4 +111,9 @@ func (c *Config) Init(dataRoot string) { } else if c.ContainerIPCSocket == "" { c.ContainerIPCSocket = "/tmp/yao.sock" } + + // Container user (for CI environments with UID mismatch) + if env := os.Getenv("YAO_SANDBOX_CONTAINER_USER"); env != "" { + c.ContainerUser = env + } } diff --git a/sandbox/config_test.go b/sandbox/config_test.go index 60bc0a78..f52d53b0 100644 --- a/sandbox/config_test.go +++ b/sandbox/config_test.go @@ -27,6 +27,10 @@ func TestDefaultConfig(t *testing.T) { } func TestConfigInit(t *testing.T) { + // Clear any existing environment variables that might interfere + os.Unsetenv("YAO_SANDBOX_WORKSPACE") + os.Unsetenv("YAO_SANDBOX_IPC") + // Test with dataRoot cfg := &Config{} cfg.Init("/tmp/yao-test") diff --git a/sandbox/manager.go b/sandbox/manager.go index 37238c2c..d47651c6 100644 --- a/sandbox/manager.go +++ b/sandbox/manager.go @@ -162,6 +162,7 @@ func (m *Manager) createContainer(ctx context.Context, userID, chatID string) (* Image: m.config.Image, Cmd: []string{"sleep", "infinity"}, WorkingDir: m.config.ContainerWorkDir, + User: m.config.ContainerUser, // Empty string uses image default Env: []string{ "YAO_IPC_SOCKET=" + m.config.ContainerIPCSocket, }, diff --git a/sandbox/manager_test.go b/sandbox/manager_test.go index b8b8148b..a57a1229 100644 --- a/sandbox/manager_test.go +++ b/sandbox/manager_test.go @@ -40,6 +40,11 @@ func getTestDirs(prefix string) (string, string, string, error) { return workspaceRoot, ipcDir, tmpDir, nil } +// getContainerUser returns the container user from environment variable +func getContainerUser() string { + return os.Getenv("YAO_SANDBOX_CONTAINER_USER") +} + // skipIfNoDocker skips the test if Docker is not available func skipIfNoDocker(t *testing.T) *Manager { t.Helper() @@ -57,6 +62,7 @@ func skipIfNoDocker(t *testing.T) *Manager { IdleTimeout: 1 * time.Minute, MaxMemory: "512m", MaxCPU: 0.5, + ContainerUser: getContainerUser(), } m, err := NewManager(cfg) @@ -545,6 +551,7 @@ func TestConcurrencyLimit(t *testing.T) { IdleTimeout: 1 * time.Minute, MaxMemory: "256m", MaxCPU: 0.25, + ContainerUser: getContainerUser(), } m, err := NewManager(cfg) @@ -674,6 +681,7 @@ func TestCleanup(t *testing.T) { IdleTimeout: 100 * time.Millisecond, // Very short for testing MaxMemory: "256m", MaxCPU: 0.25, + ContainerUser: getContainerUser(), } m, err := NewManager(cfg) @@ -748,6 +756,7 @@ func TestManagerWithYaoApp(t *testing.T) { IdleTimeout: 5 * time.Minute, MaxMemory: "1g", MaxCPU: 1.0, + ContainerUser: getContainerUser(), } m, err := NewManager(cfg) @@ -836,6 +845,7 @@ func TestEnsureImageAutoPull(t *testing.T) { IdleTimeout: 1 * time.Minute, MaxMemory: "128m", MaxCPU: 0.25, + ContainerUser: getContainerUser(), } m, err := NewManager(cfg)