Repository navigation
Suggest agent session storage cleanup when worktrees accumulate - #338502
Conversation
Detect accumulated session worktrees, surface cleanup guidance, and provide a storage manager with safe manual and automatic lifecycle cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Users who never enabled auto-archive accumulate session worktrees that consume significant disk space. Surface a suggestion notice below the Sessions list when cleanup is worthwhile, and add a Manage Agent Session Storage editor to review and clean up eligible sessions. - Show the notice when 20+ eligible worktrees exist or eligible inactive worktrees can reclaim 5 GiB or more. Only eligible candidates count toward the worktree threshold. - Add the Manage Agent Session Storage editor with an inactivity-days filter, a worktree-only filter, and a suggestion toggle. - Gate the notice behind chat.agentSessions.sessionStorageCleanupSuggestion.enabled and support per-window suppression. - Add a Disable Session Storage Cleanup Suggestions command so screen reader users have a non-visual equivalent of Don't Show Again. - Announce the notice to screen readers and document the flow in the Sessions accessibility help dialog. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: Sandeep Somavarapu (@sandy081)Matched files:
Ladislau Szomoru (@lszomoru)Matched files:
|
Marking a session that has no worktree as done reclaims no disk space, so it does not belong in a storage manager. Report only sessions with a measured worktree and remove the "Sessions with worktrees only" filter, the hasWorktree field, and the "No worktree" storage cell. The Sessions list already owns decluttering sessions that hold no storage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Active-session protection, remote URI handling, eligibility revalidation, and bounded measurement must be corrected before cleanup is safe.
Review effort: Balanced
Findings: 1
Open (7)
Compare stable identities for active session detection · New Aggregate storage across all session worktrees · New Only suppress NotFound during root resolution · New Support compact layouts for narrow editor widths · New Add editor-scoped accessibility help · New Handle archiving promise rejections · New Remove duplicate side-effect import · New
What changed in this PR
Adds guided discovery and cleanup of stale Agent Session worktrees through a Sessions-list notice and storage-management editor.
Changes:
- Measures and caches Agent Host worktree disk usage.
- Adds cleanup eligibility, configuration migration, commands, and management UI.
- Adds accessibility messaging, keyboard dismissal, styling, and focused tests.
| File | Description |
|---|---|
src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts |
Loads Agent Sessions configuration. |
src/vs/sessions/SESSIONS.md |
Documents worktree measurement ownership. |
src/vs/sessions/services/sessions/common/sessionsProvider.ts |
Adds provider measurement API. |
src/vs/sessions/services/sessions/common/sessionsManagement.ts |
Exposes measurement through management. |
src/vs/sessions/services/sessions/browser/sessionsManagementService.ts |
Routes measurements to providers. |
src/vs/sessions/contrib/sessions/test/browser/sessionStorageCleanupNotice.test.ts |
Tests notice behavior. |
src/vs/sessions/contrib/sessions/browser/views/sessionsView.ts |
Adds the Sessions-list notice. |
src/vs/sessions/contrib/sessions/browser/views/sessionStorageCleanupNotice.ts |
Implements the cleanup notice. |
src/vs/sessions/contrib/sessions/browser/media/sessionsViewPane.css |
Styles the notice. |
src/vs/sessions/contrib/sessionInputBanners/test/browser/sessionWorktreeCleanupService.test.ts |
Tests cleanup eligibility and caching. |
src/vs/sessions/contrib/sessionInputBanners/test/browser/sessionStorageCleanupConfiguration.test.ts |
Tests setting migration. |
src/vs/sessions/contrib/sessionInputBanners/browser/sessionWorktreeCleanupService.ts |
Implements measurement, eligibility, and cleanup. |
src/vs/sessions/contrib/sessionInputBanners/browser/sessionWorktreeCleanupEditorInput.ts |
Defines the manager editor input. |
src/vs/sessions/contrib/sessionInputBanners/browser/sessionWorktreeCleanupEditor.ts |
Implements the management editor. |
src/vs/sessions/contrib/sessionInputBanners/browser/sessionStorageCleanupConfiguration.ts |
Migrates the legacy setting. |
src/vs/sessions/contrib/sessionInputBanners/browser/sessionInputBannerWidget.ts |
Adds Escape dismissal. |
src/vs/sessions/contrib/sessionInputBanners/browser/sessionInputBanners.ts |
Refactors banner-state initialization. |
src/vs/sessions/contrib/sessionInputBanners/browser/media/sessionWorktreeCleanupEditor.css |
Styles the management editor. |
src/vs/sessions/contrib/providers/agentHost/test/browser/worktreeDiskUsage.test.ts |
Tests disk traversal. |
src/vs/sessions/contrib/providers/agentHost/browser/worktreeDiskUsage.ts |
Measures worktree contents. |
src/vs/sessions/contrib/providers/agentHost/browser/baseAgentHostSessionsProvider.ts |
Implements Agent Host measurement. |
src/vs/sessions/contrib/chat/browser/sessionsChatAccessibilityHelp.ts |
Documents the cleanup notice. |
src/vs/sessions/contrib/chat/browser/chat.contribution.ts |
Registers settings, service, editor, and commands. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The dismiss (x) button used a monaco Button whose background and border come from inline styles, so the intended borderless look could not be applied from CSS and it rendered as a bordered box. Render it as a plain borderless icon button matching the session input banner's close button, with a hover background and focus-visible outline, and use IHoverService for its tooltip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Identify the active session by sessionId instead of reference equality, since the active session is a VisibleSession wrapper while getSessions returns provider facades. - Aggregate disk usage across every worktree an Agent Host session owns instead of only the first, and carry the worktree count through the cleanup model so the worktree threshold counts worktrees, not sessions. - Only suppress the protocol NotFound error when measuring worktree disk usage; auth, permission, and transport failures now surface. - Collapse the storage editor table to a stacked layout in narrow windows so Storage and Open Session are not clipped. - Add an editor-scoped accessibility help provider, focus context key, and verbosity setting, and exclude the storage editor from the window-wide chat help. - Report archiving rejections through the notification service instead of leaving an unhandled rejection. - Remove a duplicate side-effect import of agentSessionsConfiguration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The setting was tagged experimental but lacked the experiment metadata, so
ExP could not set its default through the config.<setting id> treatment.
Add experiment: { mode: 'auto' } to match the other experimental Agents
window settings.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…orktrees-notification # Conflicts: # src/vs/sessions/contrib/sessions/browser/views/sessionsView.ts
- Focus the primary actions on the cleanup outcome: "Clean Up Agent
Worktrees" for the command, editor, and Sessions-list notice, and
"Clean Up {N} Worktrees" for the modal's primary button and confirmation.
- Open a session from its title link in the table and drop the separate
Actions/Open Session column.
- Make the in-modal settings read clearly as settings: a "Cleanup Settings"
heading with a gear icon and a note that the options change your settings.
- Update the accessibility help and tests to match the new terminology and
the title-link interaction.
Keeps the settings inline in the modal and the entry point below the
Sessions list per the Agents window discussion.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Drop the redundant "return here any time" paragraph from the editor. - Move the primary cleanup button up beside the summary line, right aligned, instead of a full-width row below it. - Remove the database icon from the Sessions-list notice to reclaim horizontal space and align the text and actions to the edge. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each row in the Cleanup Settings section now has a gear button that opens the Settings editor filtered to that setting, for users who prefer to configure it there directly. The button names the setting for its hover tooltip and its screen reader label. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| } | ||
| try { | ||
| const worktrees = await this.cleanupService.getWorktrees(this.minimumAgeDays); | ||
| if (version !== this.renderVersion || !this.list || !this.summary) { |
There was a problem hiding this comment.
AI Review: Closing the whole modal during measurement disposes the pane, but leaves renderVersion and the DOM references valid. The late render registers widgets and a summary hover with an already-disposed store, losing their disposal handles. HoverService's strong map retains the detached DOM and row callbacks capturing the pane/session. This retention was reproduced after GC in a current-source browser component fixture, not a full-workbench run. Invalidate loads on clearInput/disposal, check cancellation/disposal before rendering, and add a delayed-load disposal test.
| const batch = pending.splice(0, MAX_CONCURRENT_REQUESTS); | ||
| const entries = await Promise.all(batch.map(async directory => { | ||
| const children = await connection.resourceList(directory); | ||
| return Promise.all(children.entries.map(async child => { |
There was a problem hiding this comment.
AI Review: The limit of eight bounds directories, not the child requests started by this Promise.all. A production-path fixture with 1,024 real files reached 1,024 pending RPCs and outstanding stat promises; a shared Limiter(8) capped them at eight with the same byte total. Concurrent worktrees multiply this burst on the connection serving active sessions. Bound individual filesystem requests across the walks, or aggregate on the host, and add a wide-directory concurrency test.
| } | ||
|
|
||
| for (const candidate of selected) { | ||
| await this.sessionsManagementService.archiveSession(candidate.session); |
There was a problem hiding this comment.
AI Review: Eligibility is a snapshot from the editor load. A selected session can resume through another client while confirmation is open, yet this loop still archives it. SessionsManagementService.archiveSession cancels active chats first, so cleanup stops newly running work despite the promised protection. Pass the selected inactivity cutoff into this operation and recheck each current session immediately before archiving, skipping candidates that became active, running, needs-input, pinned, recent, or otherwise ineligible. Cover a status change during confirmation.
| if (worktrees.size === 0) { | ||
| return undefined; | ||
| } | ||
| const sizes = await Promise.all([...worktrees.values()].map(worktreeUri => getWorktreeDiskUsage(connection, worktreeUri))); |
There was a problem hiding this comment.
AI Review: Remote worktree locations are client-side vscode-agent-host: URIs, but these direct connection calls forward them unchanged. The standalone host cannot resolve that scheme and returns NotFound, which the walker treats as a missing root. Existing remote worktrees are consequently omitted from the editor and suggestion totals. Apply the already-imported fromAgentHostUri exactly once before walking each root, and add a wrapped-URI regression test; local and decoded inputs should measure the same directory.
|
|
||
| private async _getMeasuredWorktrees(minimumAgeDays: number): Promise<readonly ISessionWorktree[]> { | ||
| const measuredSessionSignature = this._getWorktreeSessionSignature(); | ||
| if (this._lastMeasuredWorktrees && this._lastMeasuredSessionSignature === measuredSessionSignature && Date.now() - this._lastMeasurementAt < SCAN_CACHE_DURATION_MS) { |
There was a problem hiding this comment.
AI Review: With suggestions enabled, adding one worktree changes this global signature and remeasures every existing tree after the ten-second debounce, even when the cache is seconds old. Adding one recent session after measuring 20 worktrees caused 21 more measurements. Archive/removal also invalidates unrelated fresh sizes. Reconcile the current catalog while retaining unexpired measurements for unchanged worktree identities, and invalidate only affected entries, so ordinary session creation does not defeat the advertised hourly measurement reuse.
| })); | ||
| } | ||
|
|
||
| const worktrees = await this._measureWorktrees(minimumAgeDays); |
There was a problem hiding this comment.
AI Review: _refreshPromise coalesces automatic refreshes, but not getWorktrees(). Changing the age filter during a cold scan, or opening the editor during background measurement, starts another full traversal with its own limiter. Three overlapping loads over four unchanged sessions produced 12 measurements with six in flight; renderVersion only discarded their UI results. Share the pending size measurement across callers, then recompute each caller's age cutoff and live protection state after awaiting it.
| }, | ||
| })); | ||
| const storageCleanupNotice = this._register(this.instantiationService.createInstance(SessionStorageCleanupNotice, () => sessionsControl.focus(), status)); | ||
| sessionsContent.appendChild(storageCleanupNotice.domNode); |
There was a problem hiding this comment.
AI Review: When measurement makes this notice visible, it shrinks the flex-sized list container without calling sessionsControl.layout. ListView retains the larger logical viewport and clamps scrolling too early, leaving the final rows clipped until a sidebar/window layout. A current-source browser fixture reproduced this, including the inverse mismatch on dismissal. Notify the owning view when the notice changes height, or observe the list container and run its existing layout path.
| header.appendChild(selectAll.domNode); | ||
| this.rowDisposables.add(selectAll.onChange(() => { | ||
| this.selectedSessionIds = selectAll.checked ? new Set(eligible.map(worktree => worktree.session.sessionId)) : new Set(); | ||
| this.renderRows(); |
There was a problem hiding this comment.
AI Review: Toggling Select All with Space or Enter synchronously rebuilds the table and removes the focused checkbox. Focus moves to BODY, so a second press no longer toggles the selection. Keep the header checkbox mounted while updating selections, or restore focus to its replacement, and add a keyboard regression test.
| await this.load(); | ||
| } | ||
|
|
||
| focusAutomaticCleanup(): void { |
There was a problem hiding this comment.
AI Review: Ordinary command/notice opening relies on pane.focus(), but this editor inherits the no-op Composite.focus(). Focus stays on the modal group's wrapper outside the tracked container; the new cleanup context remains false and Alt+F1 selects chat help. Only the special automatic-section path focuses a control. Override focus() to focus or restore an appropriate cleanup control and test the normal no-argument opening path.
| days.value = String(value > 0 ? value : 15); | ||
| text.classList.toggle('disabled', !checkbox.checked); | ||
| }; | ||
| update(); |
There was a problem hiding this comment.
AI Review: With modal editors enabled, use Configure in Settings to change an automatic-cleanup policy from 15 days to 0, then close only Settings with Ctrl+W. The pinned cleanup pane returns showing a checked checkbox and enabled 15-day input although the policy is disabled; setInput only reloads worktrees. Subscribe to configuration changes affecting this setting and call update(), as the suggestion toggle already does. Add coverage for returning from Settings.



Fixes #338039
Why
Before auto-archive-on-PR-merge settings existed, sessions accumulated and their worktrees now consume significant disk space. Users have no visibility into this and no guided way to clean it up.
What
Suggestion notice — appears below the Sessions list when cleanup is worthwhile:
Only eligible candidates count toward the worktree threshold, so the notice is always actionable.
Manage Agent Session Storage editor — review and clean up eligible sessions, with an inactivity-days filter (7/15/30/60/90), a worktree-only filter, and a toggle for the suggestion itself. Only fully eligible sessions are listed.
Dismissal and reset
XorEscapefalseat application scopeMeasurements are cached for one hour; changing the days filter recomputes from cache without re-measuring disk.
Settings
chat.agentSessions.sessionStorageCleanupSuggestion.enabledgates the notice.Accessibility
Validation
npm run typecheck-client— cleannpm run valid-layers-check— clean