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:

  1. Runs git rev-parse --show-toplevel to obtain the top‑level checkout path
  2. For linked worktrees, computes the main repo root by stripping the -worktrees suffix 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() uses git rev-parse --git-common-dir to identify linked worktrees
  • Resolution: resolveProject() combines git rev-parse --show-toplevel with 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.ts expose 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →