Skip to content

Commit bfbc332

Browse files
binikingiclaude
andcommitted
refactor: use persistent parent dir for multi-repo worktrees
Instead of creating worktrees inside each original repo's .worktrees/ directory, create them all inside a single persistent parent dir at ~/.agent-deck/multi-repo-worktrees/<branch>-<id>/. Each worktree is named after its repo (e.g., server/, webapp/), making them co-located and avoiding permission prompts from the agent trying to access files across separate repo directories. For multi-repo without worktree, the same parent dir layout is used with symlinks instead. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 6ae032c commit bfbc332

File tree

1 file changed

+85
-61
lines changed

1 file changed

+85
-61
lines changed

internal/ui/home.go

Lines changed: 85 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5914,69 +5914,50 @@ func (h *Home) createSessionInGroupWithWorktreeAndOptions(
59145914
if multiRepoEnabled && len(additionalPaths) > 0 {
59155915
inst.MultiRepoEnabled = true
59165916
inst.AdditionalPaths = additionalPaths
5917-
// Create temp working directory (resolve symlinks for consistent path comparison on macOS)
5918-
tempDir := filepath.Join(os.TempDir(), "agent-deck-sessions", inst.ID)
5919-
if mkErr := os.MkdirAll(tempDir, 0755); mkErr != nil {
5920-
return sessionCreatedMsg{err: fmt.Errorf("failed to create multi-repo temp dir: %w", mkErr)}
5921-
}
5922-
if resolved, evalErr := filepath.EvalSymlinks(tempDir); evalErr == nil {
5923-
tempDir = resolved
5924-
}
5925-
inst.MultiRepoTempDir = tempDir
5926-
// Update tmux session working directory to temp dir
5927-
if inst.GetTmuxSession() != nil {
5928-
inst.GetTmuxSession().WorkDir = tempDir
5929-
}
5917+
allPaths := inst.AllProjectPaths()
59305918

5931-
// For non-Claude agents, create symlinks in temp dir so they can access repos from cwd
5932-
if !session.IsClaudeCompatible(tool) {
5933-
allPaths := inst.AllProjectPaths()
5934-
dirnames := session.DeduplicateDirnames(allPaths)
5935-
for i, p := range allPaths {
5936-
_ = os.Symlink(p, filepath.Join(tempDir, dirnames[i]))
5919+
if worktreeBranch != "" {
5920+
// Multi-repo + worktree: create a persistent parent dir with all worktrees inside.
5921+
// Layout: ~/.agent-deck/multi-repo-worktrees/<branch>-<id>/<repo-name>/
5922+
home, _ := os.UserHomeDir()
5923+
sanitizedBranch := strings.ReplaceAll(worktreeBranch, "/", "-")
5924+
sanitizedBranch = strings.ReplaceAll(sanitizedBranch, " ", "-")
5925+
parentDir := filepath.Join(home, ".agent-deck", "multi-repo-worktrees",
5926+
fmt.Sprintf("%s-%s", sanitizedBranch, inst.ID[:8]))
5927+
if mkErr := os.MkdirAll(parentDir, 0o755); mkErr != nil {
5928+
return sessionCreatedMsg{err: fmt.Errorf("failed to create multi-repo worktree dir: %w", mkErr)}
59375929
}
5938-
}
5930+
if resolved, evalErr := filepath.EvalSymlinks(parentDir); evalErr == nil {
5931+
parentDir = resolved
5932+
}
5933+
inst.MultiRepoTempDir = parentDir
59395934

5940-
// Multi-repo worktree: create per-repo worktrees when worktree is also enabled
5941-
if worktreeBranch != "" {
5942-
allPaths := inst.AllProjectPaths()
5943-
wtSettings := session.GetWorktreeSettings()
5935+
// Create worktrees inside parentDir, named after each repo
5936+
dirnames := session.DeduplicateDirnames(allPaths)
59445937
var newProjectPath string
59455938
var newAdditionalPaths []string
59465939
for i, p := range allPaths {
5940+
wtPath := filepath.Join(parentDir, dirnames[i])
59475941
if git.IsGitRepo(p) {
59485942
repoRoot, rootErr := git.GetWorktreeBaseRoot(p)
59495943
if rootErr != nil {
59505944
uiLog.Warn("multi_repo_worktree_skip", slog.String("path", p), slog.String("error", rootErr.Error()))
5945+
// Copy path as-is into the parent dir via symlink
5946+
_ = os.Symlink(p, wtPath)
59515947
if i == 0 {
5952-
newProjectPath = p
5953-
} else {
5954-
newAdditionalPaths = append(newAdditionalPaths, p)
5955-
}
5956-
continue
5957-
}
5958-
wtPath := git.WorktreePath(git.WorktreePathOptions{
5959-
Branch: worktreeBranch,
5960-
Location: wtSettings.DefaultLocation,
5961-
RepoDir: repoRoot,
5962-
SessionID: git.GeneratePathID(),
5963-
Template: wtSettings.Template(),
5964-
})
5965-
if err := os.MkdirAll(filepath.Dir(wtPath), 0o755); err != nil {
5966-
uiLog.Warn("multi_repo_worktree_mkdir_fail", slog.String("error", err.Error()))
5967-
if i == 0 {
5968-
newProjectPath = p
5948+
newProjectPath = wtPath
59695949
} else {
5970-
newAdditionalPaths = append(newAdditionalPaths, p)
5950+
newAdditionalPaths = append(newAdditionalPaths, wtPath)
59715951
}
59725952
continue
59735953
}
59745954
if err := git.CreateWorktree(repoRoot, wtPath, worktreeBranch); err != nil {
59755955
uiLog.Warn("multi_repo_worktree_create_fail", slog.String("path", p), slog.String("error", err.Error()))
5956+
_ = os.Symlink(p, wtPath)
59765957
if i == 0 {
5977-
newProjectPath = p
5958+
newProjectPath = wtPath
59785959
} else {
5979-
newAdditionalPaths = append(newAdditionalPaths, p)
5960+
newAdditionalPaths = append(newAdditionalPaths, wtPath)
59805961
}
59815962
continue
59825963
}
@@ -5992,16 +5973,49 @@ func (h *Home) createSessionInGroupWithWorktreeAndOptions(
59925973
newAdditionalPaths = append(newAdditionalPaths, wtPath)
59935974
}
59945975
} else {
5995-
// Non-git paths used as-is
5976+
// Non-git paths: symlink into parent dir
5977+
_ = os.Symlink(p, wtPath)
59965978
if i == 0 {
5997-
newProjectPath = p
5979+
newProjectPath = wtPath
59985980
} else {
5999-
newAdditionalPaths = append(newAdditionalPaths, p)
5981+
newAdditionalPaths = append(newAdditionalPaths, wtPath)
60005982
}
60015983
}
60025984
}
60035985
inst.ProjectPath = newProjectPath
60045986
inst.AdditionalPaths = newAdditionalPaths
5987+
} else {
5988+
// Multi-repo without worktree: create a persistent parent dir with symlinks.
5989+
home, _ := os.UserHomeDir()
5990+
parentDir := filepath.Join(home, ".agent-deck", "multi-repo-worktrees", inst.ID[:8])
5991+
if mkErr := os.MkdirAll(parentDir, 0o755); mkErr != nil {
5992+
return sessionCreatedMsg{err: fmt.Errorf("failed to create multi-repo dir: %w", mkErr)}
5993+
}
5994+
if resolved, evalErr := filepath.EvalSymlinks(parentDir); evalErr == nil {
5995+
parentDir = resolved
5996+
}
5997+
inst.MultiRepoTempDir = parentDir
5998+
5999+
// Create symlinks for all paths
6000+
dirnames := session.DeduplicateDirnames(allPaths)
6001+
var newProjectPath string
6002+
var newAdditionalPaths []string
6003+
for i, p := range allPaths {
6004+
linkPath := filepath.Join(parentDir, dirnames[i])
6005+
_ = os.Symlink(p, linkPath)
6006+
if i == 0 {
6007+
newProjectPath = linkPath
6008+
} else {
6009+
newAdditionalPaths = append(newAdditionalPaths, linkPath)
6010+
}
6011+
}
6012+
inst.ProjectPath = newProjectPath
6013+
inst.AdditionalPaths = newAdditionalPaths
6014+
}
6015+
6016+
// Update tmux session working directory to the parent dir
6017+
if inst.GetTmuxSession() != nil {
6018+
inst.GetTmuxSession().WorkDir = inst.MultiRepoTempDir
60056019
}
60066020
}
60076021

@@ -6238,25 +6252,35 @@ func (h *Home) forkSessionCmdWithOptions(
62386252
if len(source.MultiRepoWorktrees) > 0 {
62396253
inst.MultiRepoWorktrees = append([]session.MultiRepoWorktree{}, source.MultiRepoWorktrees...)
62406254
}
6241-
tempDir := filepath.Join(os.TempDir(), "agent-deck-sessions", inst.ID)
6242-
if mkErr := os.MkdirAll(tempDir, 0755); mkErr != nil {
6243-
return sessionForkedMsg{err: fmt.Errorf("failed to create multi-repo temp dir: %w", mkErr), sourceID: sourceID}
6255+
// Create a new persistent dir for the fork with symlinks to shared worktrees
6256+
home, _ := os.UserHomeDir()
6257+
parentDir := filepath.Join(home, ".agent-deck", "multi-repo-worktrees", inst.ID[:8])
6258+
if mkErr := os.MkdirAll(parentDir, 0o755); mkErr != nil {
6259+
return sessionForkedMsg{err: fmt.Errorf("failed to create multi-repo dir: %w", mkErr), sourceID: sourceID}
62446260
}
6245-
if resolved, evalErr := filepath.EvalSymlinks(tempDir); evalErr == nil {
6246-
tempDir = resolved
6261+
if resolved, evalErr := filepath.EvalSymlinks(parentDir); evalErr == nil {
6262+
parentDir = resolved
62476263
}
6248-
inst.MultiRepoTempDir = tempDir
6264+
inst.MultiRepoTempDir = parentDir
62496265
if inst.GetTmuxSession() != nil {
6250-
inst.GetTmuxSession().WorkDir = tempDir
6251-
}
6252-
// Recreate symlinks for non-Claude agents
6253-
if !session.IsClaudeCompatible(inst.Tool) {
6254-
allPaths := inst.AllProjectPaths()
6255-
dirnames := session.DeduplicateDirnames(allPaths)
6256-
for i, p := range allPaths {
6257-
_ = os.Symlink(p, filepath.Join(tempDir, dirnames[i]))
6266+
inst.GetTmuxSession().WorkDir = parentDir
6267+
}
6268+
// Recreate symlinks/entries in new parent dir pointing to source worktree paths
6269+
allPaths := inst.AllProjectPaths()
6270+
dirnames := session.DeduplicateDirnames(allPaths)
6271+
var newProjectPath string
6272+
var newAdditionalPaths []string
6273+
for i, p := range allPaths {
6274+
linkPath := filepath.Join(parentDir, dirnames[i])
6275+
_ = os.Symlink(p, linkPath)
6276+
if i == 0 {
6277+
newProjectPath = linkPath
6278+
} else {
6279+
newAdditionalPaths = append(newAdditionalPaths, linkPath)
62586280
}
62596281
}
6282+
inst.ProjectPath = newProjectPath
6283+
inst.AdditionalPaths = newAdditionalPaths
62606284
}
62616285

62626286
if err := inst.Start(); err != nil {

0 commit comments

Comments
 (0)