How Pi-Web Resolves Project Roots with Git Worktrees: Inside the `resolveProject` Algorithm

Pi-Web determines the project root by executing git rev-parse to locate the common .git directory, distinguishes linked worktrees from the main checkout by comparing gitDir and commonDir, and employs a 60-second TTL cache to optimize session grouping and file-system access control.

The agegr/pi-web repository implements sophisticated Git worktree detection to maintain stable project identities across multiple checkouts. Understanding pi-web project root resolution worktrees logic is essential for developers extending the session management or file explorer features. The core implementation resides in lib/worktree.ts, specifically within the resolveProject function that powers both the session loader and the worktree API.

The Core Resolution Algorithm in lib/worktree.ts

The resolveProject(cwd) function serves as the single source of truth for determining whether a directory represents a main repository, a linked worktree, or a standalone project.

Caching Strategy and Deleted Directory Handling

Before invoking Git, the function checks globalThis.__piProjectCache to avoid redundant operations. If the cwd no longer exists on disk, the system falls back to inferRemovedWorktree, which recognizes paths ending with -worktrees and reconstructs the main repository root:

if (!parent.endsWith("-worktrees")) return null;
const repoRoot = parent.slice(0, -"-worktrees".length);
return { projectRoot: repoRoot, branch: basename(cwd), isWorktree: true, isTopLevel: true };

Git Query and Path Normalization

For existing directories, resolveProject executes a single Git command that returns four critical values:

git(cwd, [
  "rev-parse",
  "--path-format=absolute",
  "--show-toplevel",
  "--show-gitdir",
  "--git-common-dir",
  "HEAD"
])

The results provide:

  • commonDir: The shared .git directory for all worktrees
  • gitDir: The specific .git file or directory for this checkout
  • toplevel: The repository's top-level working directory
  • ref: The current branch or HEAD if detached

After resolving symlinks with realpathSync(cwd), the function determines if the current directory is the top-level via const isTopLevel = toplevel === realCwd.

Worktree Detection Logic

A linked worktree is identified when gitDir !== commonDir and isTopLevel is true. This condition indicates a separate working tree sharing the main repository's Git database. The final ProjectInfo object construction sets:

  • projectRoot: dirname(commonDir) for linked worktrees, otherwise the cwd itself
  • branch: The current branch name (omitted if detached HEAD)
  • isWorktree: Boolean flag for UI rendering
  • isTopLevel: Boolean indicating root directory status

Handling Removed Worktrees

When users delete a worktree directory without removing it from Git's registry, inferRemovedWorktree provides graceful degradation. By convention, Pi-Web creates worktrees under <repoRoot>-worktrees/<branch>. The fallback logic strips the -worktrees suffix to identify the main project root, allowing the session loader to correctly group orphaned sessions under their parent repository.

Integration Points: Sessions and API

The resolution data propagates through two primary integration points that enable Pi-Web's collaborative features.

Session Loading and Grouping

In lib/session-reader.ts, each session's cwd is resolved once and cached. The resulting projectRoot attaches to SessionInfo, while worktreeBranch populates only for linked worktrees:

const project = s.cwd ? projectByCwd.get(s.cwd) : undefined;
return {
  projectRoot: project?.projectRoot ?? s.cwd,
  ...(project?.isWorktree && project.branch ? { worktreeBranch: project.branch } : {})
};

This enables the sidebar to visually group sessions by shared repository, regardless of whether they originate from the main checkout or a linked worktree.

The Worktree API Endpoint

The /api/worktrees route utilizes resolveProject to authorize file system access. After establishing the project root, it lists available worktrees and registers each path with allowFileRoot():

const project = await resolveProject(cwd);
const worktrees = await listWorktrees(existsSync(cwd) ? cwd : project.projectRoot);
for (const w of worktrees) allowFileRoot(w.path);

This ensures the file explorer can browse worktree contents while maintaining security boundaries.

Supporting Functions and Cache Management

Beyond resolution, lib/worktree.ts exports helper functions for worktree manipulation:

  • listWorktrees(cwd): Parses git worktree list --porcelain into structured objects, filtering prunable entries
  • addWorktree(cwd, branch): Creates new worktrees under the -worktrees/ directory and invalidates the cache
  • removeWorktree(cwd, path, force): Executes git worktree remove and clears globalThis.__piProjectCache

The global cache operates with a 60-second TTL (PROJECT_CACHE_TTL_MS), balancing performance with freshness. Mutating operations explicitly call invalidateProjectCache() to ensure immediate consistency.

Summary

  • Pi-Web project root resolution relies on git rev-parse to locate the common Git directory and distinguish linked worktrees via directory comparison.
  • The resolveProject function in lib/worktree.ts implements a 60-second TTL cache via globalThis.__piProjectCache to optimize repeated lookups.
  • Worktree detection occurs when gitDir !== commonDir, triggering special handling for project root assignment and branch metadata.
  • Removed worktrees are inferred using the -worktrees path suffix convention, maintaining session grouping integrity even after directory deletion.
  • Session loading (lib/session-reader.ts) and the worktree API (app/api/worktrees/route.ts) consume this data to enable repository-scoped grouping and secure file access.

Frequently Asked Questions

How does pi-web handle sessions opened in deleted worktree directories?

When a worktree directory is deleted but the session persists, resolveProject detects the non-existent path and invokes inferRemovedWorktree. This function recognizes the -worktrees suffix in the parent directory, returns the main repository as the projectRoot, and preserves the branch name extracted from the deleted directory's basename. Sessions then group correctly under the main repository in the UI.

What Git command does pi-web use to detect worktrees?

The system executes git rev-parse --path-format=absolute --show-toplevel --show-gitdir --git-common-dir HEAD in a single invocation. By comparing the gitDir (specific to this checkout) against the commonDir (shared across all worktrees), the code determines whether the current directory is a linked worktree.

Why does pi-web cache project resolution results?

Performance optimization drives the caching strategy. The globalThis.__piProjectCache stores ProjectInfo objects with a 60-second TTL to avoid repeated Git process spawns when loading multiple sessions from the same directory. Cache invalidation occurs immediately after addWorktree or removeWorktree operations to ensure data consistency.

Where does pi-web create new worktrees when using the API?

The addWorktree function creates worktrees under a sibling directory named <repoRoot>-worktrees/<sanitized-branch-name>. This convention enables the inferRemovedWorktree fallback to reconstruct the main repository path even when worktree directories are deleted outside the application.

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 →