-
Notifications
You must be signed in to change notification settings - Fork 189
feat(sessions): add prune command to delete closed sessions #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -297,6 +297,102 @@ function killSignalCandidates(signal: NodeJS.Signals | undefined): NodeJS.Signal | |||||
| return [normalized, "SIGKILL"]; | ||||||
| } | ||||||
|
|
||||||
| export type PruneOptions = { | ||||||
| agentCommand?: string; | ||||||
| before?: Date; | ||||||
| olderThanMs?: number; | ||||||
| includeHistory?: boolean; | ||||||
| dryRun?: boolean; | ||||||
| }; | ||||||
|
|
||||||
| export type PruneResult = { | ||||||
| pruned: SessionRecord[]; | ||||||
| bytesFreed: number; | ||||||
| dryRun: boolean; | ||||||
| }; | ||||||
|
|
||||||
| export async function pruneSessions(options: PruneOptions = {}): Promise<PruneResult> { | ||||||
| await ensureSessionDir(); | ||||||
| const entries = await loadSessionIndexEntries(); | ||||||
|
|
||||||
| let eligible = entries.filter((entry) => entry.closed); | ||||||
|
|
||||||
| if (options.agentCommand) { | ||||||
| eligible = eligible.filter((entry) => entry.agentCommand === options.agentCommand); | ||||||
| } | ||||||
|
|
||||||
| const cutoff = | ||||||
| options.before ?? | ||||||
| (options.olderThanMs != null ? new Date(Date.now() - options.olderThanMs) : undefined); | ||||||
|
|
||||||
| if (cutoff) { | ||||||
| const cutoffIso = cutoff.toISOString(); | ||||||
| eligible = eligible.filter((entry) => entry.lastUsedAt < cutoffIso); | ||||||
| } | ||||||
|
|
||||||
| const records: SessionRecord[] = []; | ||||||
| for (const entry of eligible) { | ||||||
| const record = await loadRecordFromIndexEntry(entry); | ||||||
| if (record) { | ||||||
|
||||||
| if (record) { | |
| if (record?.closed === true) { |
Copilot
AI
Apr 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bytesFreed is incremented based on stat.size before deletion, but unlink failures are swallowed. If unlink fails (permissions, transient FS errors), the command will report bytes freed even though the file remains, and still include the record in pruned. Consider only adding to bytesFreed after a successful delete and surfacing/returning deletion failures so the CLI output is accurate.
Copilot
AI
Apr 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When --include-history is set, this loops over the full dirEntries list for every pruned session, making deletion O(prunedSessions × filesInDir). With many sessions/stream segments this can get slow. Consider pre-indexing dirEntries by safeId/prefix once (or filtering to matching names and removing them from a Set as you delete) to avoid repeated full scans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The help text says "Prune sessions closed before this date" / "closed more than N days ago", but pruning is implemented using
lastUsedAt(notclosedAt). This is user-facing behavior documentation; either update the descriptions to match (e.g., "last used before"), or change the pruning logic to use closedAt if that's the intended cutoff.