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

> Learn how pi-web uses Git worktrees and project grouping for terminal session organization. Discover its REST API for managing worktrees and grouped sessions efficiently.

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

---

**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)](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)](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

### Sidebar Organization

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)](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.

```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 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)](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.

```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

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`.

```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 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)](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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/lib/session-reader.ts), with `worktreeBranch` metadata enabling visual distinction in the sidebar.
- The REST API at [`app/api/worktrees/route.ts`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`](https://github.com/agegr/pi-web/blob/main/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`.