authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-11 15:48:41-08:00
committergravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-11 15:48:41-08:00
log94420192359d68c03241f03f47cf8d670abafa15
tree92c5841054415f1d8a25830cab2d3ac21e30ff80
parentcf66f53a5559be6f0117118e07793acf35ea170b

Filter out stale worktree paths from branch listing

Worktrees in /tmp or other ephemeral locations get cleaned by the OS but remain in git's worktree list as "prunable". These stale entries caused branches to show a worktree label pointing at a non-existent directory, leading to broken state when selected. Now validates each worktree path with existsSync before including it in the branch→worktreePath map. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

1 files changed, 3 insertions(+), 0 deletions(-)

apps/server/src/git.ts+3
......@@ -1,4 +1,5 @@
11import { spawn } from "node:child_process";
2import fs from "node:fs";
23import os from "node:os";
34import path from "node:path";
45
......@@ -131,12 +132,14 @@ export async function listGitBranches(input: GitListBranchesInput): Promise<GitL
131132 defaultRef.code === 0 ? defaultRef.stdout.trim().replace(/^refs\/remotes\/origin\//, "") : null;
132133
133134 // Build branch-name → worktree-path map from porcelain output.
135 // Only include worktrees whose directories still exist on disk (skip prunable/stale ones).
134136 const worktreeMap = new Map<string, string>();
135137 if (worktreeList.code === 0) {
136138 let currentPath: string | null = null;
137139 for (const line of worktreeList.stdout.split("\n")) {
138140 if (line.startsWith("worktree ")) {
139141 currentPath = line.slice("worktree ".length);
142 if (!fs.existsSync(currentPath)) currentPath = null;
140143 } else if (line.startsWith("branch refs/heads/") && currentPath) {
141144 worktreeMap.set(line.slice("branch refs/heads/".length), currentPath);
142145 } else if (line === "") {