pingdotgg / pingdotgg/t3code

Deleting an archived thread silently orphans its git worktree

Open
#9,085 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Summary

deleteThread only runs worktree cleanup for threads present in the main thread store. Archived threads are excluded from that store, so deleting one from Settings → Archived deletes the thread and leaves its git worktree on disk permanently, with no prompt.

Why archived threads miss the cleanup

deleteThread resolves its target via resolveThreadTargetreadThreadShell (apps/web/src/hooks/useThreadActions.ts:204). On a miss it takes an early-return branch at useThreadActions.ts:285-295:

// Thread not in main store (e.g. archived thread) — dispatch delete directly.

That returns before the worktree block at useThreadActions.ts:318-430 (getOrphanedWorktreePathForThread → confirm dialog → removeWorktree).

The main store is hydrated from getShellSnapshot, whose thread query filters archived rows — listActiveThreadRows in apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts:481:

WHERE deleted_at IS NULL
  AND archived_at IS NULL

So once the client has re-hydrated, an archived thread is by construction absent from the store the delete path reads. The archived panel is fed by a separate archivedShellSnapshot (apps/web/src/lib/archivedThreadsState.ts), and its delete action calls confirmAndDeleteThread (apps/web/src/components/settings/SettingsPanels.tsx:2641) → deleteThread.

Repro

  1. Create a thread with New worktree — worktree lands in ~/.t3/worktrees/<repo>/<branch>.
  2. Archive the thread.
  3. Restart the app (or otherwise let the shell snapshot re-hydrate).
  4. Settings → Archived → delete the thread, accepting the generic Delete thread "…"? prompt.

Expected: same as deleting an unarchived thread — This thread is the only one linked to this worktree… Delete the worktree too?

Actual: no worktree prompt. The thread is deleted; the worktree directory and its git admin entry remain.

Step 3 matters. Deleting immediately after archiving within the same session can still hit the normal path, because thread.archived does not evict the thread from the client store (apps/web/src/orchestrationEventEffects.ts:49 sets clearDeletedThread: false). That's likely why this is easy to miss in manual testing.

Impact

apps/web/src/hooks/useThreadActions.ts:425 is the only call site in the app that removes a worktree, so nothing else reclaims these. Archive-then-delete is a natural workflow, and worktrees accumulate silently.

The cost isn't trivial: each is a full working tree, and if the project has a setup script it runs inside every new worktree (apps/server/src/git/GitManager.ts:2091) — so a node_modules per worktree. git worktree prune doesn't help; it only drops admin entries for directories that are already gone.

Verification

I exercised the real deleteThread with mocked dependencies (vitest, apps/web):

  • active thread with a worktree → confirm dialog shown once, removeWorktree called once
  • readThreadShell returning null (the archived case) → delete dispatched, confirm never shown, removeWorktree never called

readThreadShell was the only difference between the two cases.

Observed at commit c17d02cf.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/web/src/hooks/useThreadActions.ts, tracing the archived-thread early return around lines 285-295 against the worktree cleanup path through line 430. Review the archived delete flow in SettingsPanels.tsx and the existing mocked-dependency Vitest coverage in apps/web. Done means deleting an archived thread reaches the same worktree confirmation and removal behavior as an active thread, with tests covering both paths.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, typescript
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.