How pi‑web Handles Git Worktrees and Resolves Shared Project Roots Across Multiple Checkouts
pi‑web treats Git worktrees as a single logical project by detecting linked worktrees via git rev-parse commands, resolving the shared project root through path manipulation, and grouping all sessions under one project entry regardless of which checkout they originate from.
When working with Git worktrees, developers often maintain multiple checkouts of the same repository—each on a different branch—in separate directories. Managing these as isolated projects creates fragmentation. The pi‑web codebase solves this by unifying worktrees under a shared project root, enabling seamless session grouping and branch switching across checkouts.
Detecting Linked Worktrees
The foundation of pi‑web's worktree handling is determining whether a directory belongs to a linked worktree versus a standard Git repository or non‑Git folder.
In lib/worktree.ts, the isLinkedWorktree(cwd) function executes:
git rev-parse --git-common-dir
This command returns the path to the main repository's .git directory. When the "common" directory points to the main repo's .git rather than a local .git file or directory, the current working directory is identified as a linked worktree [source]. The UI uses this distinction to conditionally hide the worktree switcher for plain directories.
Resolving the Shared Project Root
Once a worktree is detected, pi‑web must locate the project root—the canonical directory shared by all worktrees.
The resolveProject(cwd) function in lib/worktree.ts implements this logic:
- Runs
git rev-parse --show-toplevelto obtain the top‑level checkout path - For linked worktrees, computes the main repo root by stripping the
-worktreessuffix from the parent directory [source]
The function returns a ProjectInfo object containing:
| Property | Description |
|---|---|
projectRoot |
The shared root directory for all worktrees |
isWorktree |
Boolean flag indicating worktree status |
branch |
The worktree's branch name (if applicable) |
import { resolveProject } from "@/lib/worktree";
const info = await resolveProject("/home/user/my-repo-worktrees/feature-x");
console.log(info);
/*
{
projectRoot: "/home/user/my-repo",
isWorktree: true,
branch: "feature-x"
}
*/
Managing Worktrees Programmatically
The lib/worktree.ts module provides full CRUD operations for worktree manipulation.
Listing Worktrees
listWorktrees(cwd) executes git worktree list --porcelain and parses the structured output into WorktreeInfo objects:
import { listWorktrees } from "@/lib/worktree";
const cwd = "/home/user/my-repo-worktrees/feature-x";
const worktrees = await listWorktrees(cwd);
// Returns: { path, branch, isMain, isPrunable }[]
Creating Worktrees
addWorktree(cwd, branch) creates new worktrees under the convention <repoRoot>-worktrees/<branch>:
import { addWorktree } from "@/lib/worktree";
const cwd = "/home/user/my-repo";
const { path, branch } = await addWorktree(cwd, "feature-y");
console.log(`Worktree created at ${path} on branch ${branch}`);
After creation, allowFileRoot() registers the new path so the file‑browser can access it.
Removing Worktrees
removeWorktree(cwd, worktreePath, force) handles deletion with safety guards:
import { removeWorktree } from "@/lib/worktree";
await removeWorktree(
"/home/user/my-repo", // repository root
"/home/user/my-repo-worktrees/bugfix",
true // force removal even if dirty
);
The function refuses to delete the main worktree and surfaces Git's --force requirement for dirty worktrees.
Session Integration and UI Grouping
Worktree awareness propagates through pi‑web's session system via lib/session-reader.ts. This module enriches session metadata with worktreeBranch [source], enabling the system to:
- Group all sessions from the same repository under one project entry
- Switch active worktrees via the Worktrees sidebar UI
- Re‑associate sessions with the main repository root when their worktree is deleted [AGENTS.md]
The API endpoint in app/api/worktrees/route.ts exposes these capabilities to the frontend:
// GET /api/worktrees?cwd=/home/user/my-repo
const response = await fetch("/api/worktrees?cwd=" + encodeURIComponent(cwd));
// Returns: { projectRoot, projectKey, isGit, isTopLevel, currentWorktreePath, worktrees }
Key Implementation Files
| File | Purpose |
|---|---|
lib/worktree.ts |
Core utilities for worktree detection, project root resolution, and manipulation |
app/api/worktrees/route.ts |
HTTP API wiring utilities for the frontend |
lib/session-reader.ts |
Injects worktreeBranch into session metadata |
lib/file-access.ts |
Grants read access to the shared project root |
AGENTS.md |
Design documentation for worktree grouping behavior |
Summary
- Detection:
isLinkedWorktree()usesgit rev-parse --git-common-dirto identify linked worktrees - Resolution:
resolveProject()combinesgit rev-parse --show-toplevelwith path manipulation to find the shared project root - Convention: Worktrees are created under
<repoRoot>-worktrees/<branch>for predictable organization - Integration: Session metadata carries
worktreeBranch, enabling unified project grouping across checkouts - API: REST endpoints in
app/api/worktrees/route.tsexpose operations to the frontend
Frequently Asked Questions
How does pi‑web distinguish between a regular Git repository and a linked worktree?
pi‑web runs git rev-parse --git-common-dir via isLinkedWorktree(). If the common directory points to the main repository's .git folder rather than a local .git file or directory, the path is identified as a linked worktree. This check determines whether the worktree switcher appears in the UI.
What happens to sessions when a worktree is deleted?
Sessions that pointed to a deleted worktree are automatically re‑associated with the main repository root. This graceful fallback prevents broken references and maintains project continuity, as documented in AGENTS.md under "Worktrees and project grouping".
Why does pi‑web use a -worktrees suffix convention for new worktrees?
The suffix creates a predictable, namespaced location (<repoRoot>-worktrees/<branch>) that simplifies path manipulation when resolving the shared project root. The resolveProject() function strips this suffix to locate the canonical repository directory, enabling accurate project grouping without additional configuration.
Can pi‑web's worktree detection handle nested or non‑standard directory structures?
The current implementation assumes the -worktrees convention for resolving shared roots. For directories outside this pattern, git rev-parse --show-toplevel still provides accurate top‑level detection, though the automatic main repo root derivation relies on the expected suffix structure.
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 →