Skip to content

[Bug]: Storage cleanup never prunes ~/.t3/scratch, so no-project thread folders grow forever #26406

Closed

Description

@mirsella

Area

apps/server

Steps to reproduce

  1. Use T3 with "No project" threads (the scratch project) regularly.
  2. Enable worktree cleanup with any retention (for example 3 days, or 1 day after a thread settles).
  3. Let those threads go idle well past the retention, then run storage cleanup or wait for a sweep.
  4. Inspect ~/.t3/scratch.

Expected behavior

Idle scratch thread folders should be removed by the same sweep with the same retention rules, and should appear in the storage cleanup report instead of growing silently.

Actual behavior

Nothing under ~/.t3/scratch is ever removed or listed. On my machine 40 folders accumulated in 11 days; after deleting the 6 older than 8 days by hand, 34 remain (75 MB). Disk usage grows without bound.

In the server, StorageCleanup.cleanWorktrees only builds roots from managedWorktreesDirectories (~/.t3/worktrees plus any configured directories), keeps any candidate failing roots.some(root => inside(root, realPath)) with "outside the managed worktree folder", and requires a linked worktree (.git file, git worktree remove from the project root). Scratch threads' worktreePath is ~/.t3/scratch/<name>, so every one is kept, and reportOrphanWorktrees iterates the same roots, so the panel never shows them either.

Impact

Minor bug or occasional failure. It becomes significant on machines that use no-project threads heavily, since the folders never expire.

Version or commit

0.1.4-nightly.20261027.3770

Environment

Arch Linux (CachyOS, kernel 7.3.0), desktop app.

Workaround

An external timer that deletes mtime-stale entries under ~/.t3/scratch, which is what I'm running now.

Activity

  1. mirsella commented on Oct 28, 2026

    @mirsella
    Author

    Related: #25843 and #25844 describe the same unbounded growth inside ~/.t3/worktrees; this one is about the separate scratch root. #25844's expected behavior already asks for a T3-owned scratch location that expires, which ~/.t3/scratch could be.

  2. Qnt commented on Oct 30, 2026

    @Qnt

    What happened

    Additional evidence for #26406 on stable 0.1.5: Settings → Storage cleanup lists a completed, settled scratch thread with the reason “Thread is running or has pending work”, although no run or request is pending.

    Diagnosis

    In v0.1.5, storageCleanup.ts:789–793, thread.branch === null returns the same reason as a busy thread. The affected thread has branch: null, so this guard fires before checking managed roots or whether the folder is a linked Git worktree.

    The same condition remains in main at 6575ce3. The report should distinguish missing branch metadata or scratch-folder eligibility from actual running/pending work.

    Steps to reproduce

    1. Have an existing completed scratch thread with branch: null and an existing folder.
    2. Enable a worktree cleanup rule and run Storage cleanup.
    3. Observe the misleading keep reason above.

    The user observed this report. The investigation verified the current thread state and source; it did not rerun cleanup or create a fresh reproduction thread.

    Version

    Desktop and server: 0.1.5.

    Environment

    Windows Desktop connected to its WSL2 Linux x64 server; kernel 6.18.33.2-microsoft-standard-WSL2, Node v26.12.1.

    Evidence

    status: completed
    activeRunId: null
    pendingRequestCount: 0
    branch: null
    settledAt: 2026-10-16T12:51:30.382Z
    latest run completedAt: 2026-10-16T12:09:34.964Z
    live threads referencing the folder: 1
    folder: ~/.t3/scratch/<thread-folder>
    folder contents: empty; no .git; not a symlink
    observed on: 2026-10-30
    

    Fix applied or workaround

    None applied. No database writes, configuration changes, source patches, or cleanup runs were made. Removing this verified-empty folder with rmdir is a possible local workaround; it does not fix scratch retention or the report reason.

    Filed by

    Prepared by Codex (GPT-6.1-Sol) in T3 Code via triage.

  3. joei4cm commented on Nov 12, 2026

    @joei4cm
    Contributor

    One addition to the diagnosis, from reading storageCleanup.ts on current main: scratch threads are kept even earlier than the roots check.

    In cleanWorktrees, the eligibility gate thread.branch === null || !idle returns "thread is running or has pending work" before worktreePathReason ever runs — and scratch threads never have a branch, so every settled scratch thread is kept there with that misleading reason (the one @Qnt saw in the panel). The roots.some(...) keep the issue describes applies to candidates that get past that gate.

    So a fix has two parts, both inside cleanWorktrees plus reportOrphanWorktrees:

    1. A short non-git path for candidates under ~/.t3/scratch: the same activity/settled retention rules and report entries, but none of the git machinery that follows (branchChanged, localChangesReason, resolveCommit, remotes/default-branch checks), and a recursive directory delete instead of git worktree remove.
    2. The scratch root added to the roots that reportOrphanWorktrees scans, so folders no thread owns show up in the panel too.

    The managed-worktree logic would stay as it is; scratch would be handled beside it under the same worktreeCleanupEnabled / worktreeAfterDays / worktreeSettledAfterDays settings, which is the expected behavior this issue asks for.

    Happy to implement once a maintainer confirms that scope — per CONTRIBUTING this changes what cleanup deletes, so I would rather agree on it first than open a PR that guesses.

  4. bdamokos commented on Nov 13, 2026

    @bdamokos

    One consequence the scope above doesn't cover: after a scratch folder is pruned, the thread can't start another turn. A pruned worktree is recreated from its branch at the next turn start (recreateMissingWorktree in ProviderTurnStartService.ts), but nothing recreates a scratch folder. The turn fails with "This thread's workspace folder no longer exists or is not a directory … Restore the folder at this path before retrying."

    Scheduled tasks run into this. A weekly task bound to a No project thread leaves the thread inactive and settled for longer than a short retention between runs. On 0.1.10-nightly.20261112 a folder deleted by hand made both following weekly wakes fail within seconds. Pruning by the same retention rules would make that routine.

    #21713 added the missing step: it recreates a missing folder directly under the scratch root before opening the session, and was verified against main. It was closed as stale on 12 Nov. A recreated folder starts empty, though, and unlike a worktree a scratch folder has nothing to restore its files from. Cleanup should probably also keep folders of threads that have an enabled scheduled task; storageCleanupThreadIdle doesn't currently check schedules.

    Automated comment from a coding agent. claude-opus-5-5

  5. joei4cm commented on Nov 13, 2026

    @joei4cm
    Contributor

    Thanks — that's a consequence I hadn't accounted for, and it changes the proposal.

    From reading ProviderTurnStartService.ts, recreateMissingWorktree only covers managed worktrees, so a pruned scratch folder leaves the next turn with nothing to restore, exactly as you describe. As far as I can see there are two separate ways to keep that from happening:

    1. Don't prune it in the first place when the thread has an enabled scheduled task. storageCleanupThreadIdle doesn't look at schedules today, so a thread that only wakes weekly reads as idle and settled and gets pruned between runs. Keeping the folder present means turn start never sees a missing path, and the folder keeps its files.
    2. Recreate a missing folder under the scratch root before opening the session, which is what fix(server): recreate deleted scratch folders when reopening threads #21713 did and verified against main. That also fixes the folder-deleted-by-hand case, but a recreated scratch folder starts empty, as you said — unlike a worktree it has nothing to restore from.

    Do you want both, or only the retention side (1)? Either way I'd rather agree on the scope first than open a PR that guesses, so I'll hold off until you confirm.

  6. joei4cm commented on Nov 13, 2026

    @joei4cm
    Contributor

    Thanks for picking this one up — #36369 already settles the two questions I had (scheduled-task folders, and recreating a missing scratch folder), so I'm dropping my version.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions