Skip to content

Commit dd6c66a

Browse files
fix: run the container runtime as the host user (#7)
TestCreateToRunDocker failed on Linux CI (but not macOS) on its second Init: the dependency cache hashes code/uv.lock and got "permission denied". Root cause: the container ran as root, so `uv sync` wrote uv.lock into the bind-mounted source (and populated the venv) as root. On a host where the invoking user isn't root — Linux CI — the next Init could no longer read the root-owned uv.lock to hash it. Docker Desktop's UID remapping masked this on macOS. Run the container as the invoking host user (uid:gid) so everything it writes into bind mounts is host-owned. uv's download cache defaults to $HOME/.cache, which that user can't write inside the image, so mount a dedicated host-owned cache dir and point UV_CACHE_DIR at it. Also assert init.Status is READY in the create-to-run test: Init reports failures through the response status, not the returned error, so the previous require.NoError let a failed Init through and surfaced as a confusing empty-network-mappings error later. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cd9da84 commit dd6c66a

2 files changed

Lines changed: 19 additions & 0 deletions

File tree

‎main_test.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ func testRun(t *testing.T, runtime *Runtime, ctx context.Context, identity *base
124124
ProposedNetworkMappings: networkMappings})
125125
require.NoError(t, err)
126126
require.NotNil(t, init)
127+
// Init reports failures through the response status, not the Go error, so a
128+
// bare NoError check would let a failed Init through and surface as a
129+
// confusing empty-network-mappings error further down.
130+
require.Equal(t, runtimev0.InitStatus_READY, init.GetStatus().GetState(), init.GetStatus().GetMessage())
127131

128132
instance, err := resources.FindNetworkInstanceInNetworkMappings(ctx, init.NetworkMappings, runtime.FastAPI.RestEndpoint, resources.NewNativeNetworkAccess())
129133
require.NoError(t, err)

‎runtime.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"os"
67
"path"
78
"strings"
89

@@ -126,6 +127,12 @@ func (s *Runtime) CreateRunnerEnvironment(ctx context.Context) error {
126127
return s.Wool.Wrapf(err, "cannot create docker runner")
127128
}
128129
dockerEnv.WithPause()
130+
// Run as the invoking host user. uv sync writes uv.lock into the
131+
// bind-mounted source and populates the venv; as root those files
132+
// become root-owned on the host, and a later Init that hashes uv.lock
133+
// for its dependency cache then fails with "permission denied" on any
134+
// host where the user isn't root (e.g. Linux CI).
135+
dockerEnv.WithUser(fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()))
129136

130137
instance, err := resources.FindNetworkInstanceInNetworkMappings(ctx, s.NetworkMappings, s.FastAPI.RestEndpoint, resources.NewNativeNetworkAccess())
131138
if err != nil {
@@ -147,6 +154,14 @@ func (s *Runtime) CreateRunnerEnvironment(ctx context.Context) error {
147154
if err != nil {
148155
return s.Wool.Wrapf(err, "cannot create cache location")
149156
}
157+
// uv's download cache defaults to $HOME/.cache/uv; the host user has no
158+
// home inside the image, so give uv a writable, host-owned cache mount.
159+
uvCache, err := s.LocalDirCreate(ctx, ".cache/container/uv")
160+
if err != nil {
161+
return s.Wool.Wrapf(err, "cannot create uv cache location")
162+
}
163+
dockerEnv.WithMount(uvCache, "/uv-cache")
164+
dockerEnv.WithEnvironmentVariables(ctx, resources.Env("UV_CACHE_DIR", "/uv-cache"))
150165
s.runnerEnvironment = dockerEnv
151166

152167
case s.Base.Runtime.IsNixRuntime():

0 commit comments

Comments
 (0)