Skip to content

Storage cleanup removes a fresh worktree while its first turn waits for a usage-limit reset #24389

Closed

Description

@szemroda

What happened

A fresh worktree thread's first prompt immediately hit a provider usage limit (both Claude Code and Codex quotas were exhausted). T3 Code held the thread for the reset (limitRecovery with snooze: true), but storage cleanup deleted the newly prepared worktree before the agent did any work. "Delete unchanged worktrees" was enabled.

Expected: a worktree whose task is waiting for quota keeps its checkout until the task actually runs.

Diagnosis

Three threads on this machine match, all from 2026-10-23 on 0.1.2:

  • Run oxc stack #1 went to failed within about 2 s of startedAt. The provider thread is still native-thread:pending:run:…:ordinal:1, so no provider session ever started.
  • Each thread has a pending limitRecovery (autoResume: false, snooze: true, resetAt in the future).
  • Each worktree folder is gone and deregistered from git worktree list. The branches (t3/<name>) still exist.

Source (v0.1.2, unchanged on main):

  • apps/server/src/storageCleanup.ts:119-133: storageCleanupThreadIdle treats status === "failed" as idle (added deliberately in fix(server): free worktrees for terminal thread statuses #15150) and never looks at limitRecovery. storageCleanup.ts does not reference limitRecovery at all.
  • storageCleanup.ts:839-890: with worktreeUnchanged, a checkout whose HEAD is an ancestor of the default branch is eligible immediately. A just-created worktree always qualifies, and no grace period or inactivity window applies.
  • storageCleanup.ts:1262-1269: a provider-session.updated event with status stopped/error requests a full sweep whenever any project has worktreeOnDelete on. That sweep evaluates every rule, including worktreeUnchanged. The quota failure itself is therefore a likely trigger. The hourly sweep (:1243-1249) would get there anyway.

The other enabled rules can't explain these removals: inactivity 14 d, settled 14 d, merged (no PR), and on-delete (threads not deleted). Only worktreeUnchanged fits. The exact sweep that removed them is not in the retained traces: about 110 MB of server.trace.ndjson* covers only the last ~80 minutes.

Suggested direction: treat a thread with a pending limitRecovery (or a queued/snoozed resume) as not idle in storageCleanupThreadIdle, and/or don't let worktreeUnchanged remove a worktree whose thread has never completed a run.

Steps to reproduce

  1. Settings → Storage: enable "Delete unchanged worktrees" (and "Delete worktrees of deleted threads", so failures trigger an immediate sweep).
  2. Have the selected provider at its usage limit.
  3. Start a new thread in a new worktree and send a prompt.
  4. The turn fails with the usage limit and the thread is snoozed until the reset.
  5. Within the next sweep (immediately on the provider-session stop/error event, or within an hour), the worktree folder is removed with the reason "No commits beyond the default branch".

Version

0.1.2 (also observed on 0.1.1)

Environment

Windows 11 (10.0.26200) x64, Node v26.12.1, Git 2.55.0.windows.3, desktop app with local server; providers: Claude Code (claudeAgent) and Codex

Evidence

# orchestration_v2_projection_runs (one of three; the other two look the same)
run:thread:5f5fee33-…:ordinal:1  status=failed
  requestedAt=2026-10-23T07:05:21.494Z startedAt=07:05:43.910Z completedAt=07:05:45.746Z
  provider_thread_id=…native-thread:pending%3Arun%3Athread%3A5f5fee33-…%3Aordinal%3A1

# orchestration_v2_projection_threads.payload_json
limitRecovery={"runId":"run:thread:5f5fee33-…:ordinal:1","resetAt":"2026-10-23T09:00:00.000Z","autoResume":false,"snooze":true}
worktreePath=~\.t3\worktrees\slop-points\t3-35a54979   -> folder missing
branch=t3/greeting                                     -> branch exists, not a registered worktree

# settings at the time: storageCleanup.worktreeOnDelete=true, worktreeOnMerge=true,
# worktreeAfterDays=14, worktreeSettledAfterDays=14, worktreeUnchanged=true (since turned off)

Related issues

#15150 made failed count as a terminal, cleanable status; this report is about the usage-limit-held case it didn't account for. #19327 and #22851 cover usage-limit send/resume behaviour, not cleanup. #21374 covers recreating a missing worktree. Not a duplicate of any of them.

Fix applied or workaround

Turned "Delete unchanged worktrees" off. No recurrence since. The removed checkouts' branches are intact, so resuming those threads should go through ProviderTurnStartService's "recreating missing worktree" path.

Filed by

Claude Code (Claude Opus 5.5, claude-opus-5-5) via t3 triage

Activity

  1. MarcRez33 commented on Oct 30, 2026

    @MarcRez33

    This also happens after a successful first turn, without a usage-limit failure.

    On a Linux server using Codex:

    1. Enable “Delete unchanged worktrees”.
    2. Create a new worktree thread and send a greeting.
    3. Let the agent reply successfully without changing files.

    Storage cleanup removed the checkout about 69 seconds after the reply. The thread was neither settled nor archived. Its branch and conversation remained intact.

    Traces confirm StorageCleanup removed the worktree. Inactivity and settled retention were both set to 8 days, but the unchanged rule bypasses those windows.

    Could unchanged cleanup respect the inactivity window, or require settlement before removing a newly created checkout?

    Workaround: disable “Delete unchanged worktrees”.

    Co-authored with GPT-6.1 Sol in Codex.

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