diff --git a/pkg/board/tmux.go b/pkg/board/tmux.go index a6d1779e78..e46f47fd0b 100644 --- a/pkg/board/tmux.go +++ b/pkg/board/tmux.go @@ -9,6 +9,8 @@ import ( "strconv" "strings" "sync" + + "github.com/docker/docker-agent/pkg/paths" ) // tmuxSessions manages the tmux sessions the board runs its agents in. @@ -171,6 +173,16 @@ func applyServerDefaults(ctx context.Context) { // owns sessionID and passes it via --session: the first run creates that // session, later runs resume it. // +// --config-dir, --data-dir, and --cache-dir forward the board's own +// directories so the agent resolves the same aliases, creates its worktree +// and session under the data dir the board watches, and shares the board's +// caches. Directory overrides are process-local flags, not inherited env, +// so without forwarding an agent spawned in an environment whose $HOME +// differs from the board's directories (e.g. a docker sandbox with the +// host dirs bind-mounted) would silently use its own empty config and a +// data dir the board never looks at. On a host with no overrides this +// forwards the defaults — a no-op. +// // --listen exposes the run's control plane on listenSocket (a unix socket // the board owns), so the board can observe and drive the session over HTTP // instead of scraping the terminal. @@ -188,8 +200,10 @@ func agentCommand(agent, sessionID, listenSocket, worktreeName, worktreeBase, pr if err != nil { bin = "docker-agent" } - cmd := fmt.Sprintf("%s run %s --yolo --session %s --listen %s", - shQuote(bin), shQuote(agent), shQuote(sessionID), shQuote("unix://"+listenSocket)) + cmd := fmt.Sprintf("%s run %s --yolo --config-dir %s --data-dir %s --cache-dir %s --session %s --listen %s", + shQuote(bin), shQuote(agent), + shQuote(paths.GetConfigDir()), shQuote(paths.GetDataDir()), shQuote(paths.GetCacheDir()), + shQuote(sessionID), shQuote("unix://"+listenSocket)) if worktreeName != "" { cmd += fmt.Sprintf(" --worktree=%s --worktree-base %s", shQuote(worktreeName), shQuote(worktreeBase)) } diff --git a/pkg/board/tmux_test.go b/pkg/board/tmux_test.go index c23d3be107..444309e53a 100644 --- a/pkg/board/tmux_test.go +++ b/pkg/board/tmux_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/docker/docker-agent/pkg/paths" ) func TestShQuote(t *testing.T) { @@ -20,16 +22,46 @@ func TestAgentCommand(t *testing.T) { // First run: creates the worktree from the base. cmd := agentCommand("coder", "sess1", "/tmp/a.sock", "board-abc", "origin/main", "do the thing") - assert.Contains(t, cmd, " run 'coder' --yolo --session 'sess1' --listen 'unix:///tmp/a.sock'") + assert.Contains(t, cmd, " run 'coder' --yolo ") + assert.Contains(t, cmd, " --session 'sess1' --listen 'unix:///tmp/a.sock'") assert.Contains(t, cmd, "--worktree='board-abc' --worktree-base 'origin/main'") assert.True(t, strings.HasSuffix(cmd, " 'do the thing'")) + // The board's config, data, and cache dirs are forwarded so the agent + // resolves the same aliases and creates its worktree where the board + // watches, even when the board runs with directory overrides (e.g. in a + // sandbox whose $HOME differs from the mounted host directories). + assert.Contains(t, cmd, " --config-dir "+shQuote(paths.GetConfigDir())+" ") + assert.Contains(t, cmd, " --data-dir "+shQuote(paths.GetDataDir())+" ") + assert.Contains(t, cmd, " --cache-dir "+shQuote(paths.GetCacheDir())+" ") + // Resume: no worktree flags, no prompt. cmd = agentCommand("coder", "sess1", "/tmp/a.sock", "", "", "") assert.NotContains(t, cmd, "--worktree") assert.True(t, strings.HasSuffix(cmd, "--listen 'unix:///tmp/a.sock'")) } +// TestAgentCommandForwardsDirOverrides covers the scenario the forwarding +// exists for: the board running with directory overrides. Not parallel: it +// mutates the process-global overrides, and restores them before returning +// — during the serial phase, before parallel tests resume. +func TestAgentCommandForwardsDirOverrides(t *testing.T) { + configDir, dataDir, cacheDir := t.TempDir(), t.TempDir(), t.TempDir() + paths.SetConfigDir(configDir) + paths.SetDataDir(dataDir) + paths.SetCacheDir(cacheDir) + t.Cleanup(func() { + paths.SetConfigDir("") + paths.SetDataDir("") + paths.SetCacheDir("") + }) + + cmd := agentCommand("coder", "sess1", "/tmp/a.sock", "", "", "") + assert.Contains(t, cmd, " --config-dir "+shQuote(configDir)+" ") + assert.Contains(t, cmd, " --data-dir "+shQuote(dataDir)+" ") + assert.Contains(t, cmd, " --cache-dir "+shQuote(cacheDir)+" ") +} + func TestTmuxFormatEscape(t *testing.T) { t.Parallel()