fix: mount entire .git directory for worktree support in containers

Git worktree metadata in .git/worktrees/{name} has a 'commondir' file
that references back to the parent .git directory (typically '../..').

Mounting just the worktrees subdirectory wasn't sufficient - we need
to mount the entire .git directory so that:
1. The worktree's .git file can resolve to .git/worktrees/{name}
2. The commondir reference can resolve back to the main .git
3. Git objects, refs, and other shared data are accessible
This commit is contained in:
jcfs
2026-01-12 15:43:42 +00:00
parent c5769e9b6a
commit 3c460a694b
@@ -302,7 +302,7 @@ func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstanc
// 3. Handle worktree creation/reuse if enabled
workspacePath := req.WorkspacePath
var gitWorktreeMetadataPath string // Path to git worktree metadata for container mount
var mainRepoGitDir string // Path to main repo's .git directory for container mount
if req.UseWorktree && m.worktreeMgr != nil && req.RepositoryPath != "" {
wt, err := m.getOrCreateWorktree(ctx, req)
if err != nil {
@@ -312,13 +312,15 @@ func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstanc
// Fall back to direct mount if worktree creation fails
} else {
workspacePath = wt.Path
// Git worktrees store metadata in the main repo's .git/worktrees/{name} directory
// We need to mount this into the container at the same path for git commands to work
gitWorktreeMetadataPath = filepath.Join(req.RepositoryPath, ".git", "worktrees", req.TaskID)
// Git worktrees reference the main repo's .git directory via a .git file
// The worktree metadata in .git/worktrees/{name} contains a 'commondir' file
// that points back to the main .git directory (usually '../..')
// We need to mount the entire .git directory for git commands to work
mainRepoGitDir = filepath.Join(req.RepositoryPath, ".git")
m.logger.Info("using worktree for agent",
zap.String("task_id", req.TaskID),
zap.String("worktree_path", workspacePath),
zap.String("git_metadata_path", gitWorktreeMetadataPath),
zap.String("main_repo_git_dir", mainRepoGitDir),
zap.String("branch", wt.Branch))
}
}
@@ -329,7 +331,7 @@ func (m *Manager) Launch(ctx context.Context, req *LaunchRequest) (*AgentInstanc
// 5. Build container config from registry config (use worktree path if available)
reqWithWorktree := *req
reqWithWorktree.WorkspacePath = workspacePath
containerConfig := m.buildContainerConfig(instanceID, &reqWithWorktree, agentConfig, gitWorktreeMetadataPath)
containerConfig := m.buildContainerConfig(instanceID, &reqWithWorktree, agentConfig, mainRepoGitDir)
// 6. Create and start the container
containerID, err := m.docker.CreateContainer(ctx, containerConfig)
@@ -1025,9 +1027,9 @@ func (m *Manager) updateInstanceError(instanceID, errorMsg string) {
}
// buildContainerConfig builds a Docker container config from agent registry config
// gitWorktreeMetadataPath is the host path to the git worktree metadata (e.g., /path/to/repo/.git/worktrees/{task_id})
// If non-empty, it will be mounted into the container at the same path so git commands work correctly
func (m *Manager) buildContainerConfig(instanceID string, req *LaunchRequest, agentConfig *registry.AgentTypeConfig, gitWorktreeMetadataPath string) docker.ContainerConfig {
// mainRepoGitDir is the host path to the main repository's .git directory (e.g., /path/to/repo/.git)
// If non-empty, it will be mounted into the container at the same path so git worktree commands work correctly
func (m *Manager) buildContainerConfig(instanceID string, req *LaunchRequest, agentConfig *registry.AgentTypeConfig, mainRepoGitDir string) docker.ContainerConfig {
// Build image name with tag
imageName := agentConfig.Image
if agentConfig.Tag != "" {
@@ -1051,17 +1053,18 @@ func (m *Manager) buildContainerConfig(instanceID string, req *LaunchRequest, ag
})
}
// Add git worktree metadata mount if using a worktree
// Git worktrees reference the parent repo's .git/worktrees/{name} directory via the .git file
// We need to mount this directory into the container at the same host path for git to work
if gitWorktreeMetadataPath != "" {
// Add main repository's .git directory mount if using a worktree
// Git worktrees have a .git file pointing to {repo}/.git/worktrees/{name}
// That metadata directory has a 'commondir' file pointing back to the main .git
// We need to mount the entire .git directory for git commands to work
if mainRepoGitDir != "" {
mounts = append(mounts, docker.MountConfig{
Source: gitWorktreeMetadataPath,
Target: gitWorktreeMetadataPath, // Same path inside container
Source: mainRepoGitDir,
Target: mainRepoGitDir, // Same path inside container
ReadOnly: false,
})
m.logger.Debug("added git worktree metadata mount",
zap.String("path", gitWorktreeMetadataPath))
m.logger.Debug("added main repo .git directory mount for worktree",
zap.String("path", mainRepoGitDir))
}
// Merge environment variables