How Git Worktree Integration and Project Grouping Work in pi-web

Pi-web treats Git repositories as the fundamental unit for organizing terminal sessions, automatically detecting worktrees via git rev-parse and grouping sessions under a shared projectRoot while exposing create, list, and delete operations through a dedicated REST API.

The agegr/pi-web project provides a web-based terminal interface that deeply integrates with Git workflows. Understanding how Git worktree integration and project grouping work in pi-web reveals a system that transforms repository directories into logical project containers, enabling seamless navigation between main checkouts and linked worktrees through automatic discovery and caching mechanisms.

Resolving Projects from Worktrees

The resolveProject Function

At the core of pi-web’s Git integration is the resolveProject(cwd) function defined in [lib/worktree.ts](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts#L76-L110). This utility executes a targeted Git command (git rev-parse --git-common-dir --show-toplevel --abbrev-ref HEAD) to extract three critical pieces of metadata:

  • The common Git directory – identifies the shared .git folder for worktrees
  • The top-level checkout path – the actual root of the working tree
  • The current branch name – used to label the worktree in the UI

The function distinguishes between a standard repository checkout and a linked worktree by setting an isWorktree boolean flag. It also marks whether the current directory is the repository root via isTopLevel, ensuring the UI only renders the worktree switcher at appropriate hierarchy levels.

Caching Strategy

To avoid repeated shell calls, pi-web caches project resolution results on globalThis.__piProjectCache with a 60-second TTL. This in-memory cache ensures that rapid session queries—such as those triggered by the sidebar refresh—do not spawn unnecessary Git processes.

Grouping Sessions by Repository

Enriching Session Metadata

Session aggregation occurs in [lib/session-reader.ts](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts#L22-L46) within the listAllSessions() function. For each unique working directory (cwd), pi-web invokes resolveProject() and merges the returned ProjectInfo into every SessionInfo object:

  • projectRoot – Always points to the main repository root (or the cwd for non-Git directories), serving as the grouping key
  • worktreeBranch – Populated only when project?.isWorktree is true, containing the branch name for display purposes

The frontend uses projectRoot as the primary clustering key. Consequently, all terminal sessions residing in different worktrees of the same repository collapse under a single project node in the sidebar, while the worktreeBranch property allows users to distinguish between parallel streams of work.

Worktree Management API

Pi-web exposes full worktree lifecycle management through [app/api/worktrees/route.ts](https://github.com/agegr/pi-web/blob/main/app/api/worktrees/route.ts), implementing three distinct HTTP methods.

Listing Worktrees

A GET request to /api/worktrees?cwd=<path> returns the resolved projectRoot, boolean flags for isGit and isTopLevel, and an array of existing worktrees via the internal listWorktrees function. The handler automatically invokes allowFileRoot for each discovered worktree path, ensuring the file explorer can browse them immediately.

GET /api/worktrees?cwd=/home/user/my-repo/feature-branch
{
  "projectRoot": "/home/user/my-repo",
  "isGit": true,
  "isTopLevel": true,
  "worktrees": [
    { "path": "/home/user/my-repo-worktrees/feature-branch", "branch": "feature-branch", "isMain": false },
    { "path": "/home/user/my-repo", "branch": "main", "isMain": true }
  ]
}

Creating Worktrees

The POST endpoint accepts a cwd and branch parameter, invoking addWorktree from [lib/worktree.ts](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts#L73-L88). New worktrees are created under the convention <repoRoot>-worktrees/<sanitized-branch>. The implementation validates branch names, prevents directory collisions, and registers the new path with allowFileRoot before returning the created location.

POST /api/worktrees
Content-Type: application/json

{
  "cwd": "/home/user/my-repo",
  "branch": "feature-xyz"
}
{ "path": "/home/user/my-repo-worktrees/feature-xyz", "branch": "feature-xyz" }

Removing Worktrees

The DELETE endpoint accepts cwd, path, and an optional force parameter. It executes git worktree remove against the specified path. If the worktree contains uncommitted changes, the route returns a 409 Conflict status with a dirty: true payload, prompting the UI to request explicit user confirmation before retrying with force: true.

DELETE /api/worktrees
Content-Type: application/json

{
  "cwd": "/home/user/my-repo",
  "path": "/home/user/my-repo-worktrees/old-branch",
  "force": true
}
{ "success": true }

File System Access Control

Worktree integration extends to the file explorer through allowFileRoot, imported from [lib/allowed-roots.ts](https://github.com/agegr/pi-web/blob/main/lib/allowed-roots.ts). This utility maintains an in-memory whitelist of directories that the /api/files endpoint may traverse. Every time a worktree is created or discovered, its path is added to this registry, ensuring immediate browseability even after server restarts.

Handling Removed Worktrees

When a worktree directory is deleted externally (outside of pi-web), the resolveProject function detects the missing path and falls back to inferRemovedWorktree. This logic remaps the orphaned session’s projectRoot to the main repository root rather than leaving it dangling. As a result, terminal sessions that pointed to the now-missing worktree are gracefully re-grouped under the primary repository node instead of becoming orphaned entries.

Summary

  • Pi-web uses resolveProject() in lib/worktree.ts to detect Git worktrees via git rev-parse, caching results for 60 seconds to optimize performance.
  • Sessions are grouped by projectRoot in lib/session-reader.ts, with worktreeBranch metadata enabling visual distinction in the sidebar.
  • The REST API at app/api/worktrees/route.ts supports listing, creating, and deleting worktrees, with dirty-worktree protection via 409 responses.
  • New worktrees are automatically registered with allowFileRoot from lib/allowed-roots.ts, granting immediate file system access.
  • The inferRemovedWorktree fallback ensures sessions are never orphaned when external deletions occur.

Frequently Asked Questions

How does pi-web detect if a session is running inside a Git worktree?

Pi-web executes resolveProject(cwd) from lib/worktree.ts, which runs git rev-parse to obtain the common Git directory, top-level path, and current branch. If the common directory differs from the top-level path, the function sets isWorktree: true and extracts the branch name for UI labeling.

Where does pi-web store new worktrees created through the API?

According to the addWorktree implementation in lib/worktree.ts lines 73-88, new worktrees are created in a sibling directory following the pattern <repoRoot>-worktrees/<sanitized-branch-name>. This convention keeps worktrees adjacent to but separate from the main checkout.

What happens to existing sessions when a worktree directory is deleted outside of pi-web?

When resolveProject encounters a path that no longer exists on disk, it triggers inferRemovedWorktree to remap the session’s projectRoot to the main repository root. This prevents orphaned sessions and ensures they remain grouped with their parent repository in the sidebar.

How does pi-web prevent accidental data loss when removing worktrees with uncommitted changes?

The DELETE handler in app/api/worktrees/route.ts checks the worktree status before removal. If uncommitted changes are detected, it returns a 409 status code with dirty: true, requiring the client to explicitly resubmit the request with "force": true before the server executes git worktree remove.

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 →