Skip to content

feat(sessions): add prune command to delete closed sessions#227

Open
coder999999999 wants to merge 1 commit intoopenclaw:mainfrom
coder999999999:feat/sessions-prune
Open

feat(sessions): add prune command to delete closed sessions#227
coder999999999 wants to merge 1 commit intoopenclaw:mainfrom
coder999999999:feat/sessions-prune

Conversation

@coder999999999
Copy link
Copy Markdown

Summary

  • Adds acpx sessions prune to delete closed sessions and free disk space
  • Supports --dry-run to preview what would be removed without deleting anything
  • Supports --before <date> and --older-than <days> to filter by age (--before takes precedence if both are supplied)
  • Supports --include-history to also delete event stream files (.stream.ndjson)
  • Output works in text, json, and quiet formats

Changes

  • src/session/persistence/repository.ts — core pruneSessions function; filters closed sessions from the index, deletes their JSON records, optionally deletes stream files, then rebuilds the index
  • src/cli/flags.tsSessionsPruneFlags type and parsers for --before (returns Date) and --older-than
  • src/cli/command-handlers.tshandleSessionsPrune handler
  • src/cli/command-registration.ts — registers the prune subcommand under sessions
  • src/cli/output/render.tsprintPruneResultByFormat with text, json, and quiet output
  • test/sessions-prune.test.ts — 8 tests covering all flag combinations and edge cases

Test plan

  • sessions prune --dry-run lists sessions without deleting
  • sessions prune deletes closed sessions and reports bytes freed
  • sessions prune --before 2026-01-01 respects date cutoff
  • sessions prune --older-than 30 respects day threshold
  • sessions prune --include-history removes stream files too
  • sessions prune --format json emits valid JSON
  • Open sessions are never touched

Adds `acpx sessions prune` to delete closed sessions and free disk
space. Supports filtering by agent, date cutoff, and age threshold,
with a dry-run mode to preview what would be removed.

Flags:
  --dry-run         Preview what would be pruned without deleting
  --before <date>   Prune sessions closed before a given date
  --older-than <n>  Prune sessions closed more than N days ago
  --include-history Also delete event stream files (.stream.ndjson)

When both --before and --older-than are supplied, --before takes
precedence. Output is available in text, json, and quiet formats.

Co-authored-by: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 6, 2026 22:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new acpx sessions prune CLI subcommand and underlying persistence support to delete closed session artifacts (and optionally their event streams) to reclaim disk space.

Changes:

  • Implements pruneSessions() in session persistence with age filtering, --dry-run, and optional history deletion.
  • Adds CLI flag parsing/registration and a command handler that wires flags into pruneSessions().
  • Adds output rendering for text / json / quiet formats and introduces a dedicated test suite for pruning behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/sessions-prune.test.ts Adds tests covering prune behavior, filters, dry-run, and history deletion.
src/session/session.ts Re-exports pruneSessions and its types from the session module.
src/session/persistence/repository.ts Implements pruneSessions() and related option/result types.
src/session/persistence.ts Re-exports pruneSessions and its types from persistence entrypoint.
src/cli/output/render.ts Adds printPruneResultByFormat() and byte formatting for prune output.
src/cli/flags.ts Adds SessionsPruneFlags and parsers for --before and --older-than.
src/cli/command-registration.ts Registers sessions prune subcommand and its options.
src/cli/command-handlers.ts Adds handleSessionsPrune to execute prune and render results.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const records: SessionRecord[] = [];
for (const entry of eligible) {
const record = await loadRecordFromIndexEntry(entry);
if (record) {
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pruneSessions determines eligibility from the cached index entry (entry.closed) but does not re-check the loaded record before deleting. If index.json is stale/corrupt and marks an active session as closed, this can delete an open session file. Consider filtering again on record.closed === true (and optionally record.pid == null) after loadRecordFromIndexEntry before adding it to records / deleting.

Suggested change
if (record) {
if (record?.closed === true) {

Copilot uses AI. Check for mistakes.
Comment on lines +363 to +370
try {
const stat = await fs.stat(jsonFile);
bytesFreed += stat.size;
} catch {
// file already gone
}
await fs.unlink(jsonFile).catch(() => undefined);

Copy link

Copilot AI Apr 6, 2026

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 uses AI. Check for mistakes.
Comment on lines +371 to +385
if (options.includeHistory) {
const prefix = `${safeId}.stream`;
for (const name of dirEntries) {
if (!name.startsWith(prefix)) {
continue;
}
const filePath = path.join(sessionDir, name);
try {
const stat = await fs.stat(filePath);
bytesFreed += stat.size;
} catch {
// ignore
}
await fs.unlink(filePath).catch(() => undefined);
}
Copy link

Copilot AI Apr 6, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +146 to +147
.option("--before <date>", "Prune sessions closed before this date", parsePruneBeforeDate)
.option("--older-than <days>", "Prune sessions closed more than N days ago", parseDaysOlderThan)
Copy link

Copilot AI Apr 6, 2026

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 (not closedAt). 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.

Suggested change
.option("--before <date>", "Prune sessions closed before this date", parsePruneBeforeDate)
.option("--older-than <days>", "Prune sessions closed more than N days ago", parseDaysOlderThan)
.option("--before <date>", "Prune sessions last used before this date", parsePruneBeforeDate)
.option("--older-than <days>", "Prune sessions last used more than N days ago", parseDaysOlderThan)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants