Skip to content

fix(worktrees): stop surfacing prunable git worktrees as live workspaces#8409

Open
kaynansc wants to merge 1 commit into
stablyai:mainfrom
kaynansc:fix/skip-prunable-worktrees
Open

fix(worktrees): stop surfacing prunable git worktrees as live workspaces#8409
kaynansc wants to merge 1 commit into
stablyai:mainfrom
kaynansc:fix/skip-prunable-worktrees

Conversation

@kaynansc

Copy link
Copy Markdown
Contributor

Summary

Fixes #8389.

A git worktree whose directory was deleted without git worktree prune (git's prunable state) was still enumerated as a normal workspace. It rendered as a blank pane and produced a continuous stream of pty:spawn DaemonProtocolError: Working directory "…" does not exist and fs:readDir ENOENT errors.

With this change, Orca honors git's own prunable marker:

  • Parse the prunable porcelain field (git worktree list --porcelain, Git ≥ 2.36) in both the main-process and relay worktree-list parsers, keeping the reason string (prunableReason) alongside, mirroring how locked/lockReason are handled.
  • Git < 2.36 fallback: the prunable field shares the 2.36 boundary with worktree list -z, so the existing worktree-list-z capability fallback now also probes each linked worktree path for existence (bounded concurrency, following the sparse-checkout probe precedent). Locked registrations are never marked prunable, mirroring git's own prune semantics — Orca locks worktrees for active agent sessions, so this matters.
  • Omit prunable worktrees from the detected-workspace enumeration only (buildDetectedGitWorktrees, feeding worktrees:list / worktrees:listAll). Removal, cleanup, and branch-availability flows list worktrees separately and still see prunable entries, so git worktree remove/prune affordances keep working.

Screenshots

Repro fixture: repo payments-api with a live linked worktree (feature-live) and a stale registration (feature-stale) whose directory was deleted without git worktree prune.

Before/after: the stale worktree no longer surfaces in the sidebar

Captured from a Playwright + Electron run against this branch (after) and against main (before) with the same fixture. This is a behavior change (an entry is omitted), not a styling change, so light/dark rendering is unaffected.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test (2717 files, 28,687 tests passing)
  • pnpm build
  • Added or updated high-quality tests that would catch regressions, or explained why tests were not needed

New regression coverage (all red on main, green with the fix):

  • parseWorktreeList prunable parsing (line and NUL-delimited porcelain) in both the main (src/main/git/worktree.test.ts) and relay (src/relay/git-handler-utils.test.ts) parsers.
  • Real-git integration: listWorktrees annotates a deleted-directory worktree as prunable (src/main/git/worktree-list-paths.test.ts).
  • Git < 2.36 fallback: the line-block path marks missing linked worktrees prunable while shielding locked ones and the main worktree (src/main/git/remove-worktree.test.ts).
  • Relay existence probe unit tests (src/relay/git-handler-worktree-list.test.ts).
  • Enumeration: worktrees:listAll omits prunable worktrees (src/main/ipc/worktrees.test.ts).
  • Extended the real-binary compatibility contract (src/shared/git-binary-compatibility.test.ts) with the 2.36 prunable boundary, so the CI matrix (2.25.5 / 2.38.1 / 2.49.1) exercises both the porcelain field and the fallback expectation.

Also validated end-to-end with a scratch Playwright + Electron spec (isolated userData profile): adding the fixture project on main surfaces 3 workspaces including the stale one; on this branch it surfaces exactly main + feature-live.

AI Review Report

Reviewed with an AI coding agent along two axes (repo standards + issue spec) against origin/main:

  • Cross-platform (macOS/Linux/Windows): no platform assumptions added; path comparison in tests normalizes separators; the WSL listing path reuses the existing translateWorktreePath helper before the existence probe so WSL paths are stated via their Windows-accessible form. No shortcuts/labels/Electron platform surfaces touched.
  • SSH/remote/local: the relay parser and the relay git.listWorktrees handler carry prunable through to ssh-git-provider, so SSH repos get the same filtering as local/WSL ones. The relay existence probe runs on the host that owns the filesystem, so plain stat is authoritative there.
  • Git binary compatibility: follows the repo policy — behavior-probe capability fallback (no git --version branching), narrow isUnsupportedWorktreeListZError predicate, GitCapabilityCache scoping, capability table updated in docs/reference/git-compatibility.md, CI contract extended.
  • Agent/integration compatibility: locked worktrees (used for active agent sessions) are explicitly never marked prunable by the fallback probe, matching git's prune semantics.
  • Performance: no new work on the hot -z path (git ≥ 2.36 supplies prunable for free). The existence probe only runs on the Git < 2.36 fallback branch, with bounded concurrency (8), following the existing sparse-checkout probe precedent.
  • UI quality: no visual/styling change; a broken entry is simply no longer listed.

Findings applied from the review: introduced a dedicated PRUNABLE_EXISTENCE_PROBE_CONCURRENCY constant instead of reusing the sparse-checkout one, and trimmed new "Why:" comments to the repo's 1–2-line guideline. No unresolved findings.

Security Audit

  • Inputs to the new parsing branch come from git worktree list --porcelain output (trusted local/remote git binary), not user-controlled strings; the reason text is stored as metadata only and never executed or interpolated into commands.
  • The existence probe calls fs.stat on paths git itself reported; it does not follow user-supplied paths, does not delete or mutate anything, and treats only ENOENT as a signal (other errors are ignored, preserving current behavior).
  • No changes to IPC surface shape, auth, secrets, dependencies, or command execution. Filtering happens server-side (main process/relay) before data reaches the renderer.

Notes

  • Prunable worktrees are omitted from the workspace list (the issue offered "omit" or "show as unavailable with a prune/remove affordance"; omit matches how Orca already handles worktrees pruned properly). A future "unavailable + prune" affordance could build on the now-parsed prunable/prunableReason fields.
  • Removal/cleanup/branch-availability flows intentionally keep seeing prunable registrations (they use separate listing paths), so nothing blocks git worktree prune-style cleanup.
  • WSL + Git < 2.36: the fallback probe translates paths with the existing WSL helper before stat'ing; on modern WSL git (≥ 2.36) the porcelain field is used directly.

X handle: @KaynanSampaio1

A worktree still registered in git but whose directory was deleted
(git's `prunable` state) was enumerated as a normal workspace,
producing repeated pty:spawn DaemonProtocolError / fs:readDir ENOENT
loops and a blank pane.

- Parse the `prunable` porcelain field (Git >= 2.36) in both the main
  and relay worktree-list parsers.
- For Git < 2.36 (no `prunable` field), probe each linked worktree
  path for existence on the fallback line-block path, skipping locked
  registrations to mirror git's own prunable rules.
- Omit prunable worktrees from the detected-workspace enumeration only;
  removal/cleanup flows keep seeing them.
- Extend the real-binary compatibility contract with the 2.36
  `prunable` boundary.

Fixes stablyai#8389

Claude-Session: https://claude.ai/code/session_018Rg1Bpq4GGwmz613hq6RSD
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b6bc54eb-983c-49fa-8c58-1bf11658eca6

📥 Commits

Reviewing files that changed from the base of the PR and between 26934b1 and 3012457.

📒 Files selected for processing (14)
  • docs/reference/git-compatibility.md
  • src/main/git/remove-worktree.test.ts
  • src/main/git/worktree-list-paths.test.ts
  • src/main/git/worktree.test.ts
  • src/main/git/worktree.ts
  • src/main/ipc/worktrees.test.ts
  • src/main/ipc/worktrees.ts
  • src/relay/git-handler-utils.test.ts
  • src/relay/git-handler-utils.ts
  • src/relay/git-handler-worktree-list.test.ts
  • src/relay/git-handler-worktree-list.ts
  • src/relay/git-handler.ts
  • src/shared/git-binary-compatibility.test.ts
  • src/shared/types.ts

📝 Walkthrough

Walkthrough

Git worktree parsing now preserves prunable markers and optional reasons. Older Git fallback paths probe linked worktree directories and annotate missing entries with prunable, while excluding main, bare, locked, and already annotated worktrees. Detected worktree IPC results omit prunable entries. Tests cover parsing formats, compatibility boundaries, filesystem probing, fallback behavior, and filtering. Documentation updates the compatibility behavior description.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change to hide prunable worktrees from live workspace lists.
Description check ✅ Passed The description follows the repo template and includes all required sections with substantive content.
Linked Issues check ✅ Passed The code matches #8389 by parsing prunable worktrees, adding a pre-2.36 existence fallback, and omitting them from live enumeration.
Out of Scope Changes check ✅ Passed The changes stay focused on prunable worktree handling, supporting tests, and the related compatibility doc update.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Prunable git worktrees (deleted directory) cause repeated DaemonProtocolError / ENOENT and a blank pane

2 participants