Skip to content

fix(server): clean up worktrees with initialized submodules - #20368

Merged
juliusmarminge merged 10 commits into
pingdotgg:mainfrom
ScottN-PV:fix/20355-cleanup-worktrees-with-submodules
Oct 20, 2026
Merged

juliusmarminge merged 10 commits into
pingdotgg:mainfrom
ScottN-PV:fix/20355-cleanup-worktrees-with-submodules

Conversation

@ScottN-PV

@ScottN-PV ScottN-PV commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Git refuses to remove eligible worktrees after their submodules have been initialized. This fixes #20355 by inspecting populated submodules and stored nested repositories, then using one --force only when Git requires it. Inspection failures, unpublished live refs, and policy-protected files retain the worktree. Indexed gitlinks also expose files added after deinitialization, which Git status omits.

Rebuilt from @ScottN-PV’s original contribution, preserving contributor history and merging main. Nine added cases were reduced to five in the existing suite: 146 → 142 total, with all 137 baseline cases unchanged. Four rebuilt cases fail against main. Review found and fixed file loss after deinitialization under both file-protecting policies, plus symlink-parent and index-limit false retention. Both reviewers found no confirmed code findings in round three.

Verification at bffca7f: 142/142 real-Git service tests, server typecheck, scoped lint and formatting passed on Linux/Git 2.43.0. Temporary service probes also removed clean nested worktrees through symlinked parents under both policies and with a 77,132-byte index. Nonvisual server behavior; screenshots do not apply.

Evidence: proof and limits, before-fix ignored-file loss and default-policy loss (141 passed, one failed each), final independent 142-pass run, three service probes.

Human safety approval required before merging. Inspection and forced removal are not atomic against external writers. History checks local remote-tracking refs; tag/SHA-only commits can conservatively retain clean worktrees. The default policy also conservatively retains ignored files inside deinitialized directories, apart from the existing node_modules directory exception. Bounded or unreadable metadata retains the worktree. Git 2.34.1, macOS, Windows, iOS, Android, Electron desktop and browser flows were not executed. Published-head build/typecheck, lint and all eight server shards passed. A duplicate native-fingerprint run was cancelled and superseded by a successful run; skipped jobs remain skipped. No new review findings or submitted human reviews. Human safety approval is still missing; no merge performed.

Tests: GPT-6-Astra; implementation: GPT-6.1-Sol; adversarial reviews: Claude Opus 5.5 and GPT-6-Sol. Codex and Claude Code harnesses, high reasoning effort, orchestrated through T3 Code.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 16, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — The server cleanup path now recursively inspects submodule repositories and can force-remove eligible worktrees, materially changing destructive runtime behavior. Although the change includes broad safety tests, the new recursive logic and force-removal semantics merit human review.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

Only developers with an assigned seat can use this organization's usage-based review budget, and seats here are assigned manually. Ask an admin to assign a seat, or change the review continuation mode in Billing.

Next included review available in 12 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5382f80d-f562-4d9a-b2a6-1681fefaa895
📥 Commits

Reviewing files that changed from the base of the PR and between cdaeeca and f9caddc.

📒 Files selected for processing: 2
  • apps/server/src/storageCleanup.test.ts
  • apps/server/src/storageCleanup.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 919eba7c-45b1-40a4-a396-651c5dc9ecfd
📥 Commits

Reviewing files that changed from the base of the PR and between 1b9d4ca and cdaeeca.

📒 Files selected for processing: 1
  • apps/server/src/storageCleanup.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

📝 Walkthrough

Storage cleanup now inspects submodules before removing eligible worktrees. It keeps worktrees with submodule changes, unpushed commits, or inspection problems. When checks pass and force is required, cleanup rechecks local changes before removal.

Changes: Submodule-aware cleanup

Layer / File(s) Summary
Inspect submodule state
apps/server/src/storageCleanup.ts, apps/server/src/storageCleanup.test.ts
Cleanup checks populated submodules and stored repositories for local changes and unpushed commits. It bounds inspection and keeps the worktree when inspection fails. Tests cover local edits, unpushed commits, hidden local branches, and malformed metadata.
Apply checks during worktree removal
apps/server/src/storageCleanup.ts, apps/server/src/storageCleanup.test.ts
Cleanup runs the submodule check after untracked-file cleanup. If force is required, it rechecks local changes before removal. Tests cover clean populated or deinitialized nested submodules and a late edit in a never-initialized submodule.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant StorageCleanup
  participant submoduleRemoval
  participant Git
  StorageCleanup->>submoduleRemoval: inspect submodule state
  submoduleRemoval->>Git: check metadata, status, and refs
  Git-->>submoduleRemoval: return submodule state
  submoduleRemoval-->>StorageCleanup: return keep reason or force requirement
  StorageCleanup->>Git: recheck local changes and remove worktree
Loading

Suggested reviewers: maria-rcks

Merge Risk: ⚪ Minimal · up to cdaee

Storage cleanup now removes worktrees with clean submodules and keeps those with local changes or unpushed commits. No merge-blocking risk was found.

### Architecture Summary

Architecture risk: 🔵 Low · up to cdaee

The change affects 1 system.

Changed systems: apps/server

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — apps/server (service) was modified; 2 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in apps/server/src/storageCleanup.test.ts: Added a fixture that creates a worktree containing a submodule and provides a helper for adding a nested submodule, committing it, and pushing its commit to the remote.
  • observed — Modified behavior in apps/server/src/storageCleanup.test.ts: Added cases expecting cleanup to remove a clean worktree with populated or deinitialized nested submodules after the nested commit is pushed.
  • observed — Modified behavior in apps/server/src/storageCleanup.test.ts: Added cases expecting cleanup to keep a worktree when a submodule has an unpushed commit on HEAD or a local branch.
  • observed — Modified behavior in apps/server/src/storageCleanup.test.ts: Added cases expecting cleanup to retain a submodule with a tracked edit under uncommitted-changes or an ignored .env file under any-local-files.
Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description thoroughly documents the problem, implementation, verification results, limitations, and testing environment. However, it does not provide the required explicit maintainer approval for… Add the required Scope and approval information, including a link to the approval comment or an explanation of the applicable exemption. Record the required human safety approval before merging.
Passed Passed checks: 3 passed
Check name Status Explanation
Linked Issues check Passed Issue #20355 requires server cleanup to remove eligible worktrees with initialized submodules while retaining local-change protection and the late tracked-edit guard. storageCleanup.ts recursively c…
Out of Scope Changes check Passed The reviewed changes are limited to apps/server/src/storageCleanup.ts and its focused integration tests. The changes support issue #20355. No desktop toast change or unrelated feature change is pres…
Title check Passed The title clearly and concisely describes the main server-side change: cleaning up worktrees that contain initialized submodules.
Full details: Linked Issues check

Explanation

Issue #20355 requires server cleanup to remove eligible worktrees with initialized submodules while retaining local-change protection and the late tracked-edit guard. storageCleanup.ts recursively checks populated submodules, inspects stored submodule repositories, keeps the worktree when inspection finds changes or fails, and uses --force only after the checks pass. The forced path rechecks the worktree immediately before removal. storageCleanup.test.ts covers populated and deinitialized nested submodules, unpushed refs, local changes, malformed stored metadata, and a late tracked edit.

Full details: Out of Scope Changes check

Explanation

The reviewed changes are limited to apps/server/src/storageCleanup.ts and its focused integration tests. The changes support issue #20355. No desktop toast change or unrelated feature change is present. The separate #20211 toast issue remains out of scope.

Full details: Description check

Explanation

The description thoroughly documents the problem, implementation, verification results, limitations, and testing environment. However, it does not provide the required explicit maintainer approval for scope and direction, and it states that human safety approval is still missing.

Resolution

Add the required Scope and approval information, including a link to the approval comment or an explanation of the applicable exemption. Record the required human safety approval before merging.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@ScottN-PV
ScottN-PV force-pushed the fix/20355-cleanup-worktrees-with-submodules branch from 1b9d4ca to cdaeeca Compare October 17, 2026 06:55
@juliusmarminge juliusmarminge self-assigned this Oct 19, 2026
@juliusmarminge

Copy link
Copy Markdown
Member

Taking over this one for batch worktree-git-11. I will reduce the real-Git regression tests, rebuild the smallest safe submodule cleanup change, and run independent adversarial reviews. No merge.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Oct 19, 2026
@juliusmarminge
juliusmarminge merged commit 449e317 into pingdotgg:main Oct 20, 2026
27 checks passed
aorwall added a commit to aorwall/t3code that referenced this pull request Oct 21, 2026
Merges `pingdotgg/t3code` `main` at `e64766c56a` (367 commits from
`143ab1606d`) into the fork. It branches from `main`; no other merge PR
is open.

## Numbers

- Landed: 2630 files from `git diff --stat HEAD^1 HEAD`, against 2629 in
the upstream range. The gap:
- Kept deleted: `pair.ts`, `pair.test.ts` and
`useDiscoveredLocalServers.test.ts`.
- Kept as fork files: `branding.test.ts` and
`addBrowserSurface.test.ts`.
- Extra on the merge side: four ported fork browser tests,
`fork/ThreadParentLine.tsx` and `inventory.json`.
- Fork delta: 812 files, down from 817.

## Conflicts (40, each by its preflight verdict)

- **Upstream's test prune (pingdotgg#22557):** upstream's deletions win,
fork-only tests stay, and fork adaptations of deleted upstream tests are
dropped.
- Upstream's deletion accepted for the `ThreadDetailsPrRows`,
`PreviewEmptyState`, `previewEmptyStateLogic`,
`projectScriptEditor.permissions` and `terminalSessionAvailability`
tests.
- Four fork browser tests are ported to the new
`require-disposable-render` pattern: ThreadForkDialog,
MoatlessArtifactChip, PreviewView.frame and MobileSidebarDrawer.
- **`threadRoutes.ts`, `ThreadRouteView.tsx`, `ChatView.tsx`:** take
upstream's `unlistedThreadDetail` (pingdotgg#19724), plus three fork changes:
  - An unlisted thread is "missing" once its subscription has failed.
- A closed thread is not redirected by `archivedAt`, because it is
absent from a Moatless listing and still opens by URL.
  - `onOpenRelatedThread` shows its toast only for a deleted shell.

The `unlisted-thread-adoption` inventory entry now owns
`threadRoutes.test.ts` and `ChatView.tsx`.
- **`AGENTS.md` (`decide`):** kept the fork bullet and appended
upstream's test rules and browser-test section.
- **Upstream's change with the fork delta re-stated:** `BranchToolbar`
(`showWorkspaceControls`), `GitActionsControl`, `ChatOverlayHosts`,
`FilePreviewPanel`, `projectFilesQueryState`, `usePreviewSession` (on
upstream's `inactiveAtom`) and `useThreadActions`.
- **`getSidebarForkParentThreadId`:** upstream removed it, so it moves
into `fork/ThreadParentLine.tsx`.
- **New inventory entries:** the `branding-test` path policy and the
`preview-session-use-atom-value` duplicate-add exception.
- **Sweep:** 19 owned-concern hits, all upstream-owned and taken as
upstream's. No new workflow.

## Feature classification

### Usable as-is
- Mermaid rendering through pele (pingdotgg#21472) and project icon previews
(pingdotgg#20372).
- Dialog and composer polish, PR panel fixes and centred tables.
- The Waiting thread state (pingdotgg#21500), which shows once the backend sends
`blockedOnThreadWait`.
- The even-pace tick on Limits (pingdotgg#22408).

### Unsupported in Moatless / needs implementation
- `device.easAccount` and EAS Simulator device hosts (pingdotgg#21841–pingdotgg#21846).
Added to the `UnsupportedMethodError` union, and the Integrations page
that holds them is already flag-gated.
- `projects.listIgnoredFiles` and the `ignoredFileListing` capability,
which "Add project to another machine" uses (pingdotgg#22180). Added to the
union. The dialog falls back to tracked files only while the capability
is absent.
- `pullRequests.changeBreakdown` (pingdotgg#22189, pingdotgg#22255). Its union already
carries `UnsupportedMethodError`, and the tooltip drops the split when
the call fails.
- T3 Connect reachability (pingdotgg#22172).
- Scheduled-task webhook filters and dedupe (pingdotgg#22194, pingdotgg#22234).
- `lastEventLoopStall` (pingdotgg#22213) and the `previewClose` scope (pingdotgg#22156).
- `compactIdleThreads` and `autoCompactThreshold` (pingdotgg#21682).
- MCP diff tools (pingdotgg#15464), the install id (pingdotgg#21506) and the writing-style
setting (pingdotgg#22203).

### Backend behavior to consider reproducing in Moatless
- **Settlement and waits:** `blockedOnThreadWait`, orphaned follow-ups
and delegated waits (pingdotgg#21499, pingdotgg#22581, pingdotgg#20037, pingdotgg#20025).
- **Stop and wake:** `latestRunStopRequested` stops a stopped
usage-limit thread from auto-resuming (pingdotgg#21114). A wake run settles when
the provider never opens the wake turn (pingdotgg#19196). Snooze survives a run's
completion (pingdotgg#22202, pingdotgg#17118).
- **Runs and shells:** run failures are persisted (pingdotgg#19646), and shell
updates coalesce for a slow client (pingdotgg#14859).
- **VCS:** bare repositories (pingdotgg#17340, pingdotgg#16688, pingdotgg#18152, pingdotgg#20368), stack
layers (pingdotgg#20415, pingdotgg#20388), VCS safety fixes (pingdotgg#15036, pingdotgg#19802) and PR
provider robustness (pingdotgg#18780).
- **Other:** update and restart safety (pingdotgg#22253), terminal and checkpoint
performance (pingdotgg#21242, pingdotgg#21291), resource metrics (pingdotgg#22122), provider
polling (pingdotgg#18851) and tool-token expiry (pingdotgg#18552, pingdotgg#19491).

`docs/fork/gaps.md` is updated under *Methods the backend does not
dispatch* and *Runtime fixes upstream made to its own server*.

## Verification

Full `verify.mjs` run: every check except `test` passes (duplicate-adds,
tripwires, resolution-check, unsupported-methods with ADD 2 / DROP 0 /
KEEP 2, lockfile, fmt, lint, typecheck and build).

Test failures and what was done about each:
- **Machine failures seen in earlier merges:** `shared` `AgentScope`,
`t3` `OrchestratorReplayFixtures` and `provider-opencode`
`OpenCodeServerLedger`. The `GitVcsDriver` and
`RepositoryIdentityResolver` partial-clone tests fail because of the
sandbox's `insteadOf` rewrite.
- **New and environmental:** `t3` `ProviderInstanceRegistry` reads the
sandbox's own `ANTHROPIC_*` and `CLAUDE_CONFIG_DIR`. With those unset,
all 9 of its tests pass.
- **`web`:**
- The fork's `AppRoot.test.tsx` broke on upstream's new `useEffect`
(pingdotgg#20886). It now stubs `useEffect` and passes.
- Four browser files failed to import in the parallel run and pass
alone.
- **Playwright:** 1.60 wants chromium build 1223, which this sandbox
lacks. Browser tests ran against 1243 through a symlinked
`PLAYWRIGHT_BROWSERS_PATH`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---
Moatless task:
https://moatless.soaplabstest.com/tasks/c3df6407-7ad0-483c-a46c-e8b603326150
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Storage cleanup cannot remove worktrees that have initialized git submodules

2 participants