How to Remove a Dirty Worktree in Pi Web: API Behavior and Force Deletion

When you attempt to remove a dirty worktree through Pi Web's DELETE API without the force flag, the server returns HTTP 409 Conflict containing { "error": "...", "dirty": true } to signal that uncommitted changes exist, while including force: true bypasses this protection by appending --force to the underlying Git command.

The agegr/pi-web repository provides a Next.js-based interface for managing Git worktrees via REST endpoints. Understanding how the application handles dirty worktree removal is critical for building robust UI flows that prevent accidental data loss while offering force-delete capabilities when users explicitly confirm destructive actions.

The DELETE Request Flow in /api/worktrees

When a client sends a DELETE request to /api/worktrees, the route handler in app/api/worktrees/route.ts executes a validation and removal process that delegates to Git's native worktree commands.

Input Validation and Access Control

First, the endpoint validates that both cwd and path parameters are present in the request body, then verifies the caller has permission to access the target directory (lines 78‑85). If validation fails, the request rejects before any Git operations execute, preventing unauthorized filesystem modifications.

Git Worktree Removal Execution

The handler invokes removeWorktree() from lib/worktree.ts, passing the cwd, path, and an optional force boolean (lines 88‑89). This function executes the Git command:

await git(cwd, ["worktree", "remove", ...(force ? ["--force"] : []), worktreePath]);

Without the --force flag, Git refuses to delete worktrees containing modified or untracked files, throwing an error that propagates through the helper extractGitError() (lines 18‑22) and returns to the API route handler.

Dirty Worktree Detection and Error Handling

The route's catch block specifically differentiates dirty worktree conditions from other Git failures using regex pattern matching.

The 409 Conflict Response

When Git rejects the deletion, the handler tests the error message against:

const dirty = /contains modified or untracked files|is dirty/i.test(message);

If matched, Pi Web returns HTTP 409 Conflict with the JSON payload:

{ "error": "<git-error-message>", "dirty": true }

For non-dirty errors—such as invalid paths or permission issues—the API returns 400 Bad Request without the dirty flag. This distinction enables frontend applications to render specific confirmation dialogs only when uncommitted changes actually exist, rather than for generic failures.

Practical Implementation Examples

Safe Removal of a Clean Worktree

await fetch('/api/worktrees', {
  method: 'DELETE',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ 
    cwd: '/home/me/project', 
    path: '/home/me/project-worktrees/feature-x' 
  })
});
// Returns: { success: true } when worktree is clean

Handling Dirty Worktree Errors

const response = await fetch('/api/worktrees', {
  method: 'DELETE',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ 
    cwd: '/home/me/project', 
    path: '/home/me/project-worktrees/feature-y' 
  })
});
const data = await response.json();

if (response.status === 409 && data.dirty) {
  // UI should prompt: "Worktree has local changes. Force delete?"
  console.warn('Dirty worktree detected:', data.error);
}

Force Deleting After User Confirmation

await fetch('/api/worktrees', {
  method: 'DELETE',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    cwd: '/home/me/project',
    path: '/home/me/project-worktrees/feature-y',
    force: true  // Appends --force to git worktree remove
  })
});
// Returns: { success: true } (destructive: discards uncommitted changes)

Summary

  • Pi Web's /api/worktrees endpoint validates all delete requests in app/api/worktrees/route.ts before invoking Git operations
  • The removeWorktree() function in lib/worktree.ts executes git worktree remove with an optional --force flag depending on request parameters
  • Dirty worktrees trigger HTTP 409 Conflict with { dirty: true }, while other errors return 400 Bad Request
  • The regex /contains modified or untracked files|is dirty/i identifies dirty states from Git's error output (lines 92‑95)
  • Including force: true in the request body bypasses the dirty check by appending --force to the Git command, requiring explicit user confirmation in the UI

Frequently Asked Questions

What HTTP status code does Pi Web return for a dirty worktree?

Pi Web returns 409 Conflict when attempting to remove a dirty worktree without the force flag. This specific status code differentiates dirty states from other client errors (400 Bad Request), allowing frontend applications to handle cleanup confirmations distinctly from validation or permission failures.

How do I force delete a worktree that has uncommitted changes?

Include "force": true in the DELETE request body. This passes the --force flag to git worktree remove via the removeWorktree() function, which deletes the worktree regardless of modified or untracked files. Always confirm this destructive action with the user in the UI before sending the forced request to prevent accidental data loss.

Where does Pi Web detect if a worktree is dirty?

The detection occurs in app/api/worktrees/route.ts (lines 92‑95) within the error catch block. The code examines the Git error message using a case-insensitive regex looking for "contains modified or untracked files" or "is dirty", then sets the dirty boolean in the JSON response accordingly.

What Git command does Pi Web execute to remove worktrees?

According to lib/worktree.ts (lines 19‑20), Pi Web executes git worktree remove [path] for normal deletions, or git worktree remove --force [path] when the force parameter is true. This executes inside the removeWorktree() function after the API route handler validates that the requester has access to the target directory.

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 →