Reported from live use (2026-09-08): output.db has grown to 1.32 GB.
Retention already exists — and it cannot control size
AppSettings.OutputRetentionDays (default 30) is applied at startup by MainWindow.OnLoaded → SearchService.PruneOldOutputAsync. So this is not a missing feature; it's a policy that cannot do the job, for two independent reasons.
1. The prune deletes rows but never reclaims the file
cmd.CommandText = "DELETE FROM session_output WHERE ts < $cutoff";
That's the whole of PruneOldOutputAsync (Services/SearchService.cs:296-305). SQLite marks the pages free for reuse; it does not shrink the file. The only VACUUM in the codebase is in ClearAllOutputAsync, which is the nuclear "wipe everything" path.
So the on-disk figure only ever ratchets upward. A user watching output.db grow past a gigabyte sees the retention setting doing literally nothing, because the observable quantity — file size — is not what the policy controls.
2. Age is the wrong axis
30 days is unbounded in bytes. It depends entirely on how much output was produced, which at 47 concurrent Claude sessions is a lot. Two users with identical settings can differ by an order of magnitude, and neither can predict or cap their footprint. What a user actually wants to say is "use at most N GB".
Proposal
Add a size cap with FIFO eviction, keeping the age policy as a separate, complementary axis.
Setting: AppSettings.MaxOutputDbSizeMb (0 = unlimited). Suggest defaulting to something like 512 MB, alongside the existing OutputRetentionDays.
Eviction: oldest-first by ts — the FIFO the reporter asked for — deleting in batches until the projected size is under the cap. Because SQLite won't report a smaller size until a VACUUM, the loop should evict against a row/page estimate (page_count * page_size via PRAGMA, plus freelist_count to know what's already reclaimable) rather than re-stating the file each pass.
Reclaim: the missing half. Options, cheapest first:
PRAGMA auto_vacuum=INCREMENTAL + periodic PRAGMA incremental_vacuum(N) — reclaims progressively without a full rewrite. Note this must be set before tables are created, so an existing DB needs a one-time full VACUUM to convert.
- A full
VACUUM after a large eviction. Rewrites the whole file — at 1.3 GB that is slow and needs free disk equal to the DB size, so it should not run on the UI thread or block startup.
Where it runs: not synchronously at startup. Startup is already the app's worst latency path (#82), and a VACUUM on a gigabyte-scale file would add seconds to it. Off-thread, after restore, or on idle.
FTS5: session_output is an FTS5 table, so its index shadow tables carry much of the bulk. Verify eviction shrinks those too and that optimize / rebuild isn't needed to actually recover the space.
Also worth surfacing
The user had no way to see the problem coming. Settings should show current output.db size next to the retention controls, with the manual "clear output" action that already exists nearby.
Acceptance
Reported from live use (2026-09-08):
output.dbhas grown to 1.32 GB.Retention already exists — and it cannot control size
AppSettings.OutputRetentionDays(default 30) is applied at startup byMainWindow.OnLoaded→SearchService.PruneOldOutputAsync. So this is not a missing feature; it's a policy that cannot do the job, for two independent reasons.1. The prune deletes rows but never reclaims the file
That's the whole of
PruneOldOutputAsync(Services/SearchService.cs:296-305). SQLite marks the pages free for reuse; it does not shrink the file. The onlyVACUUMin the codebase is inClearAllOutputAsync, which is the nuclear "wipe everything" path.So the on-disk figure only ever ratchets upward. A user watching
output.dbgrow past a gigabyte sees the retention setting doing literally nothing, because the observable quantity — file size — is not what the policy controls.2. Age is the wrong axis
30 days is unbounded in bytes. It depends entirely on how much output was produced, which at 47 concurrent Claude sessions is a lot. Two users with identical settings can differ by an order of magnitude, and neither can predict or cap their footprint. What a user actually wants to say is "use at most N GB".
Proposal
Add a size cap with FIFO eviction, keeping the age policy as a separate, complementary axis.
Setting:
AppSettings.MaxOutputDbSizeMb(0 = unlimited). Suggest defaulting to something like 512 MB, alongside the existingOutputRetentionDays.Eviction: oldest-first by
ts— the FIFO the reporter asked for — deleting in batches until the projected size is under the cap. Because SQLite won't report a smaller size until aVACUUM, the loop should evict against a row/page estimate (page_count * page_sizeviaPRAGMA, plusfreelist_countto know what's already reclaimable) rather than re-stating the file each pass.Reclaim: the missing half. Options, cheapest first:
PRAGMA auto_vacuum=INCREMENTAL+ periodicPRAGMA incremental_vacuum(N)— reclaims progressively without a full rewrite. Note this must be set before tables are created, so an existing DB needs a one-time fullVACUUMto convert.VACUUMafter a large eviction. Rewrites the whole file — at 1.3 GB that is slow and needs free disk equal to the DB size, so it should not run on the UI thread or block startup.Where it runs: not synchronously at startup. Startup is already the app's worst latency path (#82), and a
VACUUMon a gigabyte-scale file would add seconds to it. Off-thread, after restore, or on idle.FTS5:
session_outputis an FTS5 table, so its index shadow tables carry much of the bulk. Verify eviction shrinks those too and thatoptimize/rebuildisn't needed to actually recover the space.Also worth surfacing
The user had no way to see the problem coming. Settings should show current
output.dbsize next to the retention controls, with the manual "clear output" action that already exists nearby.Acceptance
MaxOutputDbSizeMbsetting, enforced by oldest-first evictionOutputRetentionDayskeeps working as the age axis