# How Pi‑Web Manages and Groups Git Worktrees by Project

> Learn how Pi-Web manages and groups Git worktrees by project. Discover REST endpoints for creating, listing, and removing worktrees efficiently. Optimize your Git workflow today.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: how-to-guide
- Published: 2026-08-13

---

**Pi‑Web discovers the shared Git repository root for each session, caches project metadata for 60 seconds, and groups sessions under a common project node while exposing REST endpoints to create, list, and remove worktrees.**

Pi‑Web treats the Git repository as the fundamental organizational unit for terminal sessions. When a session’s working directory resides inside a linked worktree, the application resolves the common repository root and current branch to cluster related sessions together. This architecture enables seamless git worktree management directly from the web interface without manual path configuration.

## Resolving Repository Roots and Worktree Status

Pi‑Web identifies whether a session operates inside a Git worktree through the `resolveProject` function defined in [`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts). This utility executes Git commands to locate the shared repository root and determine if the current path represents a linked worktree or the main checkout.

### The resolveProject Function

Located at lines 76‑110 of [`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts), `resolveProject(cwd)` runs `git rev‑parse` to obtain three critical pieces of information: the common Git directory, the actual top‑level checkout path, and the current branch name. The function sets boolean flags distinguishing between the main repository (`isWorktree: false`) and linked worktrees (`isWorktree: true`), while also marking the top‑level directory (`isTopLevel`) to control where the UI displays the worktree switcher.

### Caching and Performance Optimization

To prevent repeated shell calls, Pi‑Web caches results on `globalThis.__piProjectCache` with a 60‑second TTL. Subsequent requests for the same working directory return the cached `ProjectInfo` object immediately, reducing latency when the session list refreshes.

## Grouping Sessions by Project

The session reader layer enriches metadata with repository context, enabling the sidebar UI to cluster sessions under their originating project regardless of which worktree they occupy.

### Enriching Session Metadata

The `listAllSessions()` function in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) (lines 22‑46) invokes `resolveProject` for every unique `cwd` and merges the results into each `SessionInfo` record. This adds two key fields: `projectRoot`, which contains the main repository path, and `worktreeBranch`, which stores the branch name when `isWorktree` is true. The frontend uses `projectRoot` as the grouping key, ensuring all worktrees belonging to the same repository appear beneath a single project node.

### Handling Removed Worktrees

When a worktree directory disappears from disk, `resolveProject` falls back to `inferRemovedWorktree` rather than failing. This logic reassigns orphaned sessions to the main repository root, preventing them from becoming detached from their project group in the UI.

## Worktree Management API

The file [`app/api/worktrees/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/worktrees/route.ts) exposes a REST interface for worktree operations, integrating with the file system whitelist to ensure new worktrees are immediately browsable.

### Listing Existing Worktrees

A **GET** request to `/api/worktrees?cwd=/path/to/dir` returns the resolved `projectRoot`, boolean flags `isGit` and `isTopLevel`, and an array of existing worktrees via `listWorktrees`. The handler automatically calls `allowFileRoot` (from [`lib/allowed-roots.ts`](https://github.com/agegr/pi-web/blob/main/lib/allowed-roots.ts)) for each discovered path, granting the file explorer permission to browse those directories without restarting the server.

```http
GET /api/worktrees?cwd=/home/user/my-repo/feature-branch

```

```json
{
  "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 New Worktrees

The **POST** endpoint creates worktrees under a sibling directory named `<repoRoot>-worktrees/<sanitized-branch>`, as implemented in the `addWorktree` function (lines 73‑88 of [`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts)). It validates the branch name, ensures the target directory does not exist, executes `git worktree add`, and registers the new path with `allowFileRoot`.

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

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

```

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

```

### Removing Worktrees Safely

The **DELETE** endpoint executes `git worktree remove` on the specified path. If the worktree contains uncommitted changes, the route returns a 409 Conflict response containing `dirty: true`, allowing the frontend to prompt the user for a forced removal.

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

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

```

```json
{ "success": true }

```

## File System Integration

Worktree visibility depends on `allowFileRoot` from [`lib/allowed-roots.ts`](https://github.com/agegr/pi-web/blob/main/lib/allowed-roots.ts), which maintains an in-memory whitelist that the `/api/files` endpoint consults. Every time a worktree is created or listed, Pi‑Web updates this whitelist, ensuring that worktree directories remain accessible to the file explorer across server restarts.

## Summary

- **`resolveProject`** in [`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts) discovers repository roots and distinguishes main checkouts from linked worktrees using `git rev‑parse`.
- **Session metadata** in [`lib/session-reader.ts`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts) attaches `projectRoot` and `worktreeBranch` to group sessions by repository in the UI.
- **REST endpoints** in [`app/api/worktrees/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/worktrees/route.ts) provide CRUD operations for worktrees, with automatic file-root registration via `allowFileRoot`.
- **Graceful degradation** through `inferRemovedWorktree` prevents sessions from becoming orphaned when worktrees are deleted externally.
- **Caching** on `globalThis.__piProjectCache` for 60 seconds minimizes Git command overhead during rapid session list updates.

## Frequently Asked Questions

### How does pi‑web detect if a session is inside a Git worktree?

The `resolveProject` function in [`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts) executes `git rev‑parse` to compare the current working directory against the main repository path. If the resolved top‑level directory differs from the common Git directory’s parent, Pi‑Web sets `isWorktree: true` and captures the branch name in the `ProjectInfo` object.

### Where does pi‑web store newly created worktrees?

When handling a POST request to `/api/worktrees`, Pi‑Web creates worktrees under a sibling directory named `<repoRoot>-worktrees/<sanitized-branch>`, as implemented in the `addWorktree` function (lines 73‑88 of [`lib/worktree.ts`](https://github.com/agegr/pi-web/blob/main/lib/worktree.ts)). This convention keeps worktrees organized outside the main repository folder while maintaining a predictable path structure.

### What happens to sessions when a worktree directory is deleted externally?

Pi‑Web handles missing worktrees gracefully through the `inferRemovedWorktree` fallback within `resolveProject`. Instead of leaving sessions orphaned, the system reassigns them to the main repository root, ensuring they remain grouped under the correct project node in the sidebar.

### How does pi‑web prevent data loss when removing worktrees?

The DELETE endpoint in [`app/api/worktrees/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/worktrees/route.ts) attempts a standard `git worktree remove` and returns a 409 Conflict response with `dirty: true` if uncommitted changes exist. The UI can then prompt the user to confirm forced removal, preventing accidental deletion of unsaved work.