Skip to content

Sidebar lists deleted worktrees and labels detached HEAD as "detached" #118

Description

@leduckhc

Summary

The sidebar lists git worktrees that no longer exist on disk, and labels any detached-HEAD worktree with the literal string detached. Both come from the same screenshot but are two small gaps: the server does not drop stale registrations, and the app has no better label than a hardcoded word.

What was observed

A subagent had created a throwaway worktree in $TMPDIR at a detached HEAD. Its directory was deleted, but the .git/worktrees/<id> admin entry survived:

$ git worktree list
/Users/le/Work/Vibe/makit                                        2ce5fa6 [main]
/private/var/folders/.../T/pi-agent-25dcaa90-c175-4cb-f35eabf5   51a86ed (detached HEAD)
/Users/le/.worktrees/makit/feat-github-budget                     51a86ed [feat/github-budget]

The sidebar showed it as a group named detached sitting next to feat/github-budget, both reporting the identical diff (+6927 -225) because they point at the same commit. The directory was already gone — ls on that path returned "No such file or directory".

Cause

1. Stale registrations are not filtered. listWorktrees in server/src/git.ts parses git worktree list --porcelain and handles the worktree, HEAD, branch and detached lines. It does not handle prunable, which git emits for exactly this case (a registration whose working tree is missing). Those entries are forwarded to the app as ordinary worktrees.

2. detached is a hardcoded fallback, and there is nothing better to fall back to. Four call sites do worktree.branch ?? 'detached':

  • app/lib/desktop/chat/desktop_sidebar.dart:487
  • app/lib/ui/home/worktree_row.dart:26
  • app/lib/desktop/chat/new_worktree_dialog.dart:473
  • app/lib/desktop/chat/archived_sidebar_view.dart:231

The server's WorktreeEntry already carries head (the commit sha), but the app's Worktree model (app/lib/store/models.dart:501) drops it — so the UI has no sha available even though the server read one.

Suggested fix

  • Skip prunable entries in listWorktrees, or surface the flag so the app can filter/mark them. A worktree whose directory is gone is not a place you can start an agent.
  • Carry head through to the app's Worktree model and label a detached worktree with a short sha (51a86ed) instead of the word detached. That also disambiguates two detached worktrees, which currently render identically.
  • Consider whether temp/throwaway worktrees should appear as groups at all.

Notes

  • Worth deciding what a live detached worktree should show, not just a stale one — the sha label covers both.
  • git worktree prune clears the stale entry as a workaround, but the app should not depend on the user running it.
  • Found while triaging the crash in fix(app): bump riverpod to 3.4.2 to stop the mid-build crash #116. Unrelated to that crash — it was investigated as a possible trigger and ruled out by test (the repos-snapshot-loses-a-worktree path is green).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions