Skip to content

[FEATURE]: Session Lifecycle Management — unified storage reclamation, auto-archival, and child session cleanup #16101

Closed as not planned

Description

@guazi04

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Problem

OpenCode sessions grow unboundedly. There is no TTL, no auto-cleanup, no storage cap, and no post-compaction reclamation. Over time this leads to significant disk usage that users have reported across multiple issues.

Real-world data from 3 weeks of active use:

Metric Value
Database size (opencode.db) 774 MB
Sessions 1,812 (248 root, 1,564 child)
Messages 50,818
Parts (PartTable rows) 176,596 (626 MB = 81% of DB)
Archived sessions 0 (no auto-archival exists)
Pruned parts still retaining full output 2,689 (14.15 MB wasted)

86% of all sessions are child sessions (from task() calls), and they hold 75% of total part storage.

Scope: DB growth vs. filesystem artifacts

This issue focuses on SQLite database growth (opencode.db + WAL/SHM). Filesystem-level storage leaks like tmp_pack_* file accumulation (#8749, #14811) are a separate problem with different root causes (snapshot system). Both contribute to disk pressure, but they require different solutions.

Current State

OpenCode has some building blocks, but they don't form a complete lifecycle:

Capability Exists? Gap
Prune (mark old tool outputs) ✅ compaction.ts:59-99 Sets time.compacted on ToolStateCompleted parts but never clears output or attachments — data stays in DB
Compaction (LLM summarization) ✅ Summary is tiny (~0.07 MB for 166 compactions) but pre-compaction parts are never reclaimed
Archive (mark session archived) ✅ Session.setArchived() + REST API Not surfaced in TUI or CLI — users can't archive sessions without hitting the API directly
Remove (delete session + cascade children) ✅ Session.remove() Manual only, no batch/auto cleanup
Auto-cleanup / TTL ❌ Nothing exists
Child session lifecycle ❌ No independent cleanup; only cascade-deleted with parent
Storage statistics / management UI ❌ No way to see DB size or per-session storage usage
SQLite VACUUM ❌ Never called — deleted/cleared data doesn't free disk space (passive WAL checkpoint exists at startup but only flushes, does not truncate WAL or reclaim row space)

Related Issues

This problem has surfaced across many issues, each addressing a piece:

More related issues and PRs

Storage growth (filesystem-level, separate root cause):

Related open PRs:

None of these take a holistic view. This issue aims to unify them under a coherent session lifecycle management design.

Proposal

1. Post-Compaction Storage Reclamation

After compaction produces a summary, the original parts before the compaction point have served their purpose — the summary is usually sufficient for the model to continue.

The reclamation target is ToolStateCompleted parts — specifically their output (string) and attachments (FilePart[]) fields, which make up the vast majority of storage. Part rows and metadata (tool name, input args, timestamps) would be preserved.

Two levels of reclamation:

Conservative: Clear output and attachments from tool parts that already have time.compacted set. This is a natural extension of the current prune logic — prune already identifies these parts as disposable and the rendering layer already checks time.compacted (see message-v2.ts:637-638), it just doesn't follow through on clearing the stored content.

Aggressive: After compaction completes, clear output and attachments from all ToolStateCompleted parts before the compaction point — not just the pruned ones. The compaction summary usually captures the full context needed to continue. This approach is proven in practice by oh-my-opencode's compaction strategy, which produces comprehensive summaries including child session records, task state, and key discoveries — and the model continues working effectively.

Whether to apply conservative or aggressive reclamation could be a user-configurable option, extending the existing config.compaction namespace (which already has auto and prune booleans).

In my database, parts account for 626 MB (81% of 774 MB). The compaction summaries total just 0.07 MB. The potential savings are orders of magnitude.

2. Auto-Archival for Inactive Sessions

Sessions that haven't been active for N days could be auto-archived. The time_archived column already exists in the schema but is never set automatically.

This would require adding a new configuration key (e.g., in the config.compaction or a new config.session namespace) since no session lifecycle config exists today. The auto-archive check could run at startup or on a periodic interval.

3. Child Session Lifecycle

Child sessions (86% of all sessions in my database) are created by task() and hold 75% of storage. Once a child session completes and returns its result to the parent, its detailed parts are rarely needed again.

Note: the parent session already captures the child's result in its own tool part output. The child's SubtaskPart only holds input metadata (prompt, description, agent) — the actual result lives in the parent's tool output. This means the child's full part history is largely redundant once the parent acknowledges the result.

Suggestion: Completed child sessions could have their parts reclaimed after a configurable retention period (e.g., 24h or N days). This preserves the ability to inspect recent child sessions for debugging while reclaiming storage from older ones. Immediate deletion is too aggressive — users may want to review child session details shortly after completion.

4. Session Management Tooling

Users currently have no visibility into storage usage and limited management options:

  • archive — supported via Session.setArchived() + REST API, but not surfaced in CLI or TUI — should be exposed as opencode session archive and a TUI action
  • No storage statistics — a command like opencode session stats could show DB size, session count, largest sessions
  • No batch cleanup — opencode session prune (see PR feat(cli): add session prune command for cleanup #12631) for removing old/archived sessions and reclaiming part storage
  • No VACUUM — SQLite does not automatically reclaim disk space from deleted rows. After batch cleanup, running VACUUM would reclaim disk space. Optionally, PRAGMA wal_checkpoint(TRUNCATE) could also be offered to shrink a large WAL file when the DB is idle. (Currently OpenCode only runs a passive checkpoint at startup via db.ts:83, which flushes WAL contents but does not truncate the file.)

Non-goals / Invariants

  • Preserve recent transcript UX — reclamation should not affect sessions the user is actively viewing or recently used. A retention window ensures users can still scroll back and inspect recent tool outputs.
  • Preserve task_id resume capability — child session data should be retained long enough for task_id-based resume to work. The retention period in Proposal feat: model config persistence #3 addresses this.
  • Don't VACUUM during active sessions — VACUUM requires exclusive access. It should only be triggered by explicit user action or at shutdown.
  • Reclamation is opt-in or gradual — start with the conservative approach; let users opt into aggressive reclamation.

MVP Path

A practical incremental path:

  1. Land existing PRs — fix: clear tool output and attachments when pruning to prevent memory leak #7049 and feat(opencode): prune old tool outputs before compaction #14827 already clear tool output on prune. Getting these merged would immediately address the "prune marks but doesn't clear" gap.
  2. Expose archive in TUI/CLI — small surface area change, high user impact.
  3. Add opencode session stats — give users visibility into storage before asking them to manage it.
  4. Add checkpoint/VACUUM to cleanup flow — ensure deleted data actually frees disk space.
  5. Then iterate — auto-archival, child session lifecycle, and aggressive reclamation can build on this foundation.

Background

I discovered this problem while building a plugin that reads session history across projects for weekly work summaries. The 774 MB database (3 weeks, 10 projects) made it clear that session storage needs active lifecycle management. The plugin use case also highlighted that Session.listGlobal() is only accessible via an experimental API route — cross-project session access could benefit from first-class support.

Summary

The core ask is: sessions should have a lifecycle, not just a birth. The building blocks exist (prune, archive, remove, compaction) but they aren't connected into a coherent lifecycle. Adding post-compaction reclamation, auto-archival, child session cleanup, and management tooling would address the storage growth problem systematically rather than piecemeal.

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    perfIndicates a performance issue or need for optimization
    needs:complianceThis means the issue will auto-close after 2 hours.
    on Mar 5, 2026
  2. changed the title [-][DESIGN]: Session Lifecycle Management — unified storage reclamation, auto-archival, and child session cleanup[/-] [+][FEATURE]: Session Lifecycle Management — unified storage reclamation, auto-archival, and child session cleanup[/+] on Mar 5, 2026
  3. github-actions commented on Mar 5, 2026

    @github-actions
    Contributor

    Thanks for updating the issue! It now meets our contributing guidelines. The needs:compliance label has been removed.

  4. added a commit that references this issue on Apr 8, 2026
    65b06f8
  5. assigned and unassigned on Apr 26, 2026
  6. removed
    perfIndicates a performance issue or need for optimization
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  7. blain3white commented on Jun 9, 2026

    @blain3white

    This maps closely to what I'm building in Clean My Agent — local-first lifecycle management for OpenCode sessions including archival, safe deletion, and export. Posting in case it's useful as a reference while the native feature is designed.

  8. gigimento commented on Jun 17, 2026

    @gigimento

    Same issue here — my opencode.db grew to 1.25 GB causing OOM crash in the Desktop (Electron) app. The part table alone was 152 MB with base64-encoded attachments (PDFs/images) from long sessions. Had to manually delete sessions and VACUUM to reclaim ~1 GB. An auto-retention policy with configurable maxSizeMB / maxAgeDays would have prevented this entirely. Also, session delete should VACUUM automatically.

  9. claell commented on Jul 25, 2026

    @claell

    Strong +1 on the storage-reclamation and stats parts of this. On the TTL / auto-cleanup part, I'd ask that it ship opt-in and off by default.

    Reclaiming pruned tool outputs, VACUUM, and clearing compacted part payloads are all lossless — they solve most of the 774 MB in your table without touching anything a user might want to read again. Time-based deletion of whole sessions is a different category and should be treated as such.

    The cautionary case is Claude Code, which defaults to deleting session transcripts after 30 days with no prompt and no recovery: anthropics/claude-code#59248 (labelled data-loss) and anthropics/claude-code#62476. Many users treat session history as durable working knowledge and have no idea a sweep exists until it has already run.

    Suggested: retention_days unset by default; opencode session prune --older-than 30d --dry-run as the primary interface; and if auto-prune is ever enabled, surface it explicitly on first run.

  10. steve02081504 commented on Sep 18, 2026

    @steve02081504

    Concrete requirement from a daily user (Windows), adding to the TTL / auto-cleanup part of this issue:

    Auto-delete sessions idle for longer than N days.

    • Trigger should be based on inactivity (time.updated) rather than time.created — a session that is still being resumed must not be swept.

    • Ships opt-in and off by default (agreeing with @claell above). Recommended config shape, under a new session lifecycle namespace or config.compaction:

      {
        "session": {
          "retention_days": 30,        // unset/0 = disabled (default: disabled)
          "retention_mode": "archive"  // "archive" | "delete"
        }
      }
    • Check can run on startup and/or on a periodic interval, in the background, with no interactive prompt.

    • Deletion should cascade to child sessions (as Session.remove() already does) and run VACUUM afterwards, otherwise disk space is not actually reclaimed.

    • A non-destructive CLI surface would help too, e.g. opencode session prune --older-than 30d --dry-run.

    Current workaround is a manual loop over opencode session list --format json (which exposes updated/created in epoch ms) + opencode session delete <id>, but there is no batch/auto equivalent. opencode session list also has no age filter, so a script must parse all sessions itself.

  11. Dante-dan commented on Sep 28, 2026

    @Dante-dan
    Contributor

    I have been following this issue for a while, and I have a concrete need for opt-in cleanup. I also saw the recent session prune PRs #51254 and #51255, so I do not want to duplicate their manual CLI deletion flow.

    For a focused follow-up, could the core team review this approach before implementation?

    • Keep current behavior by default: cleanup is disabled and no sessions are deleted unless a user explicitly enables it in config.
    • Let users configure either or both rules: sessions inactive for a chosen period (for example 30 or 90 days, based on last activity) and sessions whose local working directory no longer exists.
    • Keep session history available and make manual cleanup show a preview with the matching rule/reason before deletion. Reuse the prune/removal path rather than deleting SQLite rows directly.
    • Revalidate candidates before removal; keep active/recent sessions and remote workspaces; treat filesystem access errors as unknown and preserve those sessions. Do not run VACUUM automatically.

    This is the specific behavior I need in my own use. I am happy to prepare a PR against this issue once the core team has reviewed the design. Would you approve this scope, or suggest changes before implementation?

  12. steve02081504 commented on Sep 28, 2026

    @steve02081504

    #16101 (comment)

    Update on my earlier requirement: I ended up implementing this in fount's code shell instead — activity-based retention with configurable archive/delete, cascade deletion, and VACUUM after cleanup — so I no longer need to watch this issue. Still rooting for a native version here for everyone else.

  13. AlexeyShirshov commented on Sep 30, 2026

    @AlexeyShirshov

    I'd like to contribute an archive action to the existing TUI session list, without replacing the dialog.

    • Add a configurable shortcut, defaulting to Ctrl+S.
    • First press asks for confirmation; second press archives the highlighted session without opening it.
    • Changing the selected session or action resets confirmation.
    • Keep Ctrl+D deletion unchanged.
    • Remove the row after successful archiving; retain it and show an error if the request fails.

    Archive browsing and restoration would be outside this patch.

    Would this scope be acceptable?

  14. s3ntin3l8 commented on Oct 17, 2026

    @s3ntin3l8

    Another data point from a long-running host (opencode 2.0.29), in case it helps prioritise:

    • opencode.db is 17 GB, but PRAGMA freelist_count x page_size shows ~84% of the pages (~15 GB) are on the freelist; live data is only ~2.7 GB. auto_vacuum is 0 and journal_mode is wal, so the file never shrinks on its own and the space is only recoverable with a manual VACUUM.
    • 1485 sessions (1042 of them child sessions), none archived; ~513k part rows and ~126k session_message rows.
    • The legacy v1 session/message/part tables are still populated (~1.5 GB) even though every v1 session id also exists in session_v2.
    • There is no bulk or age-based way to prune: opencode session delete <id> is per session, which is impractical at this scale.

    Something like an auto_vacuum=INCREMENTAL setting (or a periodic vacuum when idle), an age-based archive/delete command, and dropping the v1 tables after migration would each address a part of this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

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