Repository navigation
tracking(storage): task storage visibility, reclamation and scoped cleanup #5825
Description
Activity
take
Plan (M2 reclamation): add bounded incremental_vacuum lane for context-offload.sqlite in HostStorageMaintenance; support explicit user-requested runtime.sqlite INCREMENTAL conversion + compact at next Host start (pre-Ready, after free-disk check); passive wal_checkpoint in maintenance loop; wire Data settings to show space reclaimed. Branch: feat/5825-m2-storage-reclamation. M1 visibility (#5832) stays separate.
Status update:
- M1 (visibility) landed in feat(storage): report Host storage usage and per-task sizes #5832.
- M2 (reclamation) is with @garvit-arora in feat(storage): reclaim context-offload SQLite free pages in Host maintenance #5855. I've suggested landing the
context-offload.sqlitelane and the report first, and splittingruntime.sqlitecompaction out (review on feat(storage): reclaim context-offload SQLite free pages in Host maintenance #5855). - M3 (
archived_at+ scoped cleanup) stays with me. This issue remains the tracker for all three, so PRs should sayPart of #5825rather thanFixes.
Open question 1 now blocks the rest of M2: where should a full
VACUUMofruntime.sqliterun? #5855 has measured both placements:- before listen, it pushes startup toward the 75 s deadline (~55 s for a 1 GB DB);
- after Ready, it blocks the serving event loop (~13 s for ~1 GB), past the 8 s client liveness timeout.
The options I see:
- a worker thread with its own connection;
- run only while no client is attached;
- pre-listen, with a startup progress state and an extended deadline;
- don't convert, and only report reclaimable space.
A maintainer call here would unblock the follow-up.
- added a commit that references this issue
on Sep 30, 2026 Status update:
- M1 (visibility): landed in feat(storage): report Host storage usage and per-task sizes #5832.
- M3 (scoped cleanup): landed in feat(sessions): record when a task was archived #5884 (durable
archived_at) and feat(desktop): clean up archived tasks by age and project #5896 (age and project filters, plus a Host-side batch removal preview and an age guard). - M2 (reclamation): feat(storage): reclaim context-offload SQLite free pages in Host maintenance #5855 (@garvit-arora) is now narrowed to the bounded
context-offload.sqliteincremental-vacuum lane. Recent reviews found no correctness blockers; it's waiting on a PR description update.
One open item. Converting
runtime.sqliteto incremental vacuum still has no decision on where its one fullVACUUMshould run:- before listen: about 55 s for a 1 GB database, against a 75 s startup deadline;
- after Ready: blocks the serving event loop for about 13 s per GB;
- in a worker;
- or never, and only report the reclaimable space.
Once #5855 lands I'll close this tracker, and move the
runtime.sqlitequestion to its own issue so it isn't lost.Next steps from #5776 continue elsewhere: retention is #5899 / #5902.
- added 2 commits that reference this issue
on Oct 8, 2026 Closing. All three steps have landed:
- M1, visibility: feat(storage): report Host storage usage and per-task sizes #5832.
- M2, reclamation: feat(storage): reclaim context-offload SQLite free pages in Host maintenance #5855. It adds a bounded
incremental_vacuumlane forcontext-offload.sqlite. @garvit-arora built the lane; I rebased it over feat(storage): opt-in retention for archived tasks #5902 and applied the last review round. - M3, scoped cleanup: feat(sessions): record when a task was archived #5884 (durable
archived_at) and feat(desktop): clean up archived tasks by age and project #5896 (age and project filters, Host-side batch preview).
The one open item, where
runtime.sqlite's one-time fullVACUUMshould run before it can switch to incremental vacuum, now lives in #6000 with the measurements and options. Retention, the next step from #5776, landed in #5902, with notices in #5961.One note for the record: the #5855 squash commit (
c542bc757) went in subject-only, so it does not carry theGenerated-by: Claude Codetrailer that the follow-up commits had. Claude Code made a substantive contribution to that PR's final revision, as its description states.
Step 1 of the task lifecycle plan agreed in #5776. It makes local storage visible, reclaims space that deletion already frees logically, and lets users clean up archived tasks by scope. It adds no automatic archiving or deletion; those are later, opt-in steps tracked in #5776. The Host computes and executes; Desktop only presents.
Baseline:
main@0323714.Current state
runtime.sqlitenever shrinks.auto_vacuum=NONE(sqlite-runtime-schema.ts).wal_checkpoint, setsjournal_size_limit, or runsVACUUM.context-offload.sqliteis set up for reclamation but never reclaims.auto_vacuum=INCREMENTAL(sqlite-context-offload-schema.ts).incremental_vacuum.session_metadataandSessionHeaderhave no archive time, andcommitted_atis overwritten on every metadata update, so it can't serve as one.session.removeonce per session, serially (session-row-actions.ts).Plan: three PRs, each usable on its own
M1: storage visibility (read-only)
storage.usage.query, query mode, Ready only.database,transcript,artifacts,context_offload,worktrees(count only),memory,usage_historyandother.freelist_count * page_size).M2: reclamation
incremental_vacuumlane forcontext-offload.sqlitein the existingHostStorageMaintenanceloop.runtime.sqliteconversion toauto_vacuum=INCREMENTAL. It needs one fullVACUUM, which needs roughly 2x the database size in free disk, so it runs only on explicit user request ("Compact database"). The Host persists the request and runs it at the next start, before Ready, next to migrations, after a free-disk check.M3: scoped cleanup
archived_at.NULL, show "archive time unknown", and are excluded from age filters.session.remove.previewtakes a list of sessions and reports tasks, child tasks, bytes and worktrees that would be removed.session.remove.Questions for maintainers
runtime.sqliteacceptable? The alternative is to only report the reclaimable space and never convert.archived_atland as a small standalone PR first, since step 2 (retention) reuses it?session.remove?Risks
node:sqliteis synchronous, so vacuum steps must stay small to avoid blocking the Host event loop.usage_model_call_attempts), so the database may stay larger than users expect. The UI should call this out.Refs #5776, #5341, #5394, #5038, #4984, #5605.