Git Worktree Resolution and Project Grouping in pi-web: How It Works
pi-web treats each session's current working directory as a potential Git repository, resolving linked worktrees via git rev-parse calls in lib/worktree.ts and grouping sessions by project root in lib/session-reader.ts to unify multiple checkouts under a single UI node.
pi-web is a web-based terminal session manager that requires precise Git repository context to organize multiple terminal sessions effectively. The system implements sophisticated git worktree resolution to detect whether a session's working directory belongs to a linked worktree or the main repository checkout, enabling intelligent project grouping in the session sidebar.
Core Worktree Resolution Logic
All Git detection logic resides in lib/worktree.ts, which exports the primary resolveProject function used throughout the application to classify directory contexts and extract repository metadata.
The resolveProject Function
The resolveProject(cwd) function (lines 76-110) executes four parallel git rev-parse commands to gather repository metadata:
import { resolveProject } from '@/lib/worktree';
const cwd = '/home/user/repo-feature';
const info = await resolveProject(cwd);
console.log(info);
// {
// projectRoot: '/home/user/repo',
// branch: 'feature',
// isWorktree: true,
// isTopLevel: true
// }
The function captures:
commonDir: Path to the shared.gitdirectory using--git-common-dirgitDir: The worktree-specific.gitpath via--git-dirtoplevel: Absolute path of the checkout root via--show-toplevelref: Current branch name via--abbrev-ref HEAD
Detecting Linked Worktrees vs Top-Level Checkouts
After normalizing the current working directory with realpathSync, the system classifies the location through two boolean checks:
- Top-level checkout: Determined when
toplevel === realCwd - Linked worktree: Identified when
gitDir !== commonDir && isTopLevel
For linked worktrees, the projectRoot becomes the parent directory of commonDir (the main repository root), while standard checkouts use the cwd itself. This distinction allows pi-web to treat all worktrees of the same repository as belonging to a single project.
Handling Removed Worktrees
When a session points to a directory that no longer exists under the <repoRoot>-worktrees convention, the inferRemovedWorktree function (lines 68-74) fabricates a ProjectInfo object. This fallback mechanism folds the orphaned session back under the main repository root, preventing UI breakage while maintaining project associations.
Caching Strategy
To avoid spawning repeated Git processes, pi-web caches resolution results on globalThis.__piProjectCache for 60 seconds (lines 40-47). This TTL-based cache ensures rapid session list refreshes without overwhelming the system with redundant rev-parse calls.
Project Grouping for the Session Sidebar
The UI groups terminal sessions by repository using logic in lib/session-reader.ts, which bridges the worktree detection with the frontend display.
Session Resolution and Metadata Attachment
The loadAllSessions() function (lines 21-28) iterates through all active sessions, calling resolveProject for each unique cwd and storing results in a projectByCwd map. When constructing each SessionInfo object (lines 30-44), the system:
- Sets
projectRoottoproject?.projectRoot ?? s.cwd - Attaches
worktreeBranchwhen the cwd represents a linked worktree
This metadata enables the sidebar to display sessions under a unified project tree node regardless of whether they originate from the main checkout or any linked worktree.
Grouping Implementation
Sessions sharing the same projectRoot—whether from the main repository or linked worktrees—appear grouped together in the UI. Subdirectories inside a repository maintain their own identity with isTopLevel === false, ensuring accurate context preservation.
// Session grouping – obtaining the list that the UI renders
import { listAllSessions } from '@/lib/session-reader';
const sessions = await listAllSessions();
const byProject = new Map<string, typeof sessions>();
for (const s of sessions) {
const key = s.projectRoot;
if (!byProject.has(key)) byProject.set(key, []);
byProject.get(key)!.push(s);
}
console.table([...byProject.entries()].map(([proj, list]) => ({
projectRoot: proj,
sessionCount: list.length,
worktreeBranches: [...new Set(list.map(x => x.worktreeBranch).filter(Boolean))]
})));
API Endpoints for Worktree Management
The app/api/worktrees/route.ts file exposes HTTP endpoints that mirror the internal resolution logic, secured by path validation (lines 8-14).
Listing and Resolving Worktrees
The GET /api/worktrees?cwd= endpoint (lines 26-44) returns a JSON object containing projectRoot, isGit, isTopLevel, and an array of available worktrees. This powers the "Switch worktree" UI component.
Creating and Removing Worktrees
- POST
/api/worktrees: Creates new linked worktrees viaaddWorktree(cwd, branch)(lines 66-68) - DELETE
/api/worktrees: Removes worktrees safely usingremoveWorktree(lines 75-89)
// Add a new worktree via the backend API (client-side)
await fetch('/api/worktrees', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cwd: '/home/user/repo', branch: 'new-branch' })
}).then(r => r.json()).then(console.log);
// → { path: '/home/user/repo-worktrees/new-branch', branch: 'new-branch' }
Listing Available Worktrees
The listWorktrees function imported from lib/worktree.ts enumerates all worktrees for a given project root, distinguishing the main checkout from linked worktrees:
// List all worktrees for a project (used by the sidebar switcher)
import { listWorktrees } from '@/lib/worktree';
const worktrees = await listWorktrees('/home/user/repo');
worktrees.forEach(w => {
console.log(`${w.isMain ? '[main]' : '[worktree]'} ${w.path} → ${w.branch}`);
});
Summary
- pi-web implements git worktree resolution in
lib/worktree.tsusinggit rev-parsewith--git-common-dirand--git-dirflags to distinguish linked worktrees from main checkouts. - The
resolveProjectfunction returns aProjectInfoobject containingprojectRoot,branch,isWorktree, andisTopLevelflags, cached for 60 seconds to optimize performance. - Project grouping occurs in
lib/session-reader.ts, where sessions sharing the sameprojectRootare aggregated under a single UI node regardless of their specific checkout path. - The REST API in
app/api/worktrees/route.tsexposes worktree management with path validation, supporting creation, deletion, and resolution operations. - Removed worktrees are handled gracefully via
inferRemovedWorktree, ensuring sessions always maintain valid project associations.
Frequently Asked Questions
How does pi-web detect if a directory is a linked Git worktree?
pi-web detects linked worktrees by comparing the gitDir (worktree-specific Git directory) with the commonDir (shared .git directory). When gitDir !== commonDir and the path is a top-level checkout, the system classifies it as a linked worktree and sets isWorktree: true in the ProjectInfo object, as implemented in lib/worktree.ts.
What happens to sessions when a worktree directory is deleted?
When a worktree directory no longer exists, the inferRemovedWorktree function (lines 68-74 in lib/worktree.ts) fabricates a fallback ProjectInfo object that associates the session with the main repository root. This prevents UI errors while maintaining the session's project grouping context.
How does pi-web group sessions from different worktrees of the same repository?
The system groups sessions by projectRoot, which for linked worktrees resolves to the main repository root (the parent of the common Git directory). The loadAllSessions function in lib/session-reader.ts attaches this projectRoot to each session, causing the UI to render all worktrees of the same repository under a single collapsible tree node.
Can pi-web create new Git worktrees through its API?
Yes, the POST /api/worktrees endpoint accepts a cwd and branch parameter, calling addWorktree(cwd, branch) from lib/worktree.ts to create a new linked worktree. The endpoint validates that the requested path is within an allowed file root before executing Git commands.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →