feat(daemon): prune dead sessions past retention limits on startup#296
feat(daemon): prune dead sessions past retention limits on startup#296mgabor3141 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
Try this PRcurl -sSfL https://gmux.app/install-pr.sh | sh -s -- 296Built from |
ab3384a to
efc7f28
Compare
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@greptile review |
|
| Filename | Overview |
|---|---|
| services/gmuxd/internal/sessionmeta/sessionmeta.go | Core retention logic — adds RetentionPolicy, DefaultRetention (with env overrides + overflow guard), prune (age-out then count cap), and effectiveTime. Logic is sound; minor dead-code nit in prune's clock setup. |
| services/gmuxd/internal/sessionmeta/sessionmeta_test.go | Five new retention tests covering age-out, count cap, no-policy, undatable-survives, and env overrides. Missing a case for GMUX_SESSION_RETENTION_MAX=0 disabling the count limit, but all happy paths are well covered. |
| services/gmuxd/cmd/gmuxd/main.go | One-line wiring change: passes DefaultRetention() to New via WithRetention; clean and minimal. |
| packages/paths/paths.go | Doc comment only — adds retention policy description to SessionDir; accurate and matches the implementation. |
Sequence Diagram
sequenceDiagram
participant main as gmuxd/main
participant store as Store.Sweep
participant prune as Store.prune
participant disk as Disk (sessions/)
main->>store: Sweep()
store->>disk: ReadDir(sessions/)
disk-->>store: []DirEntry
loop each entry
store->>disk: Read meta.json
disk-->>store: "Session (Alive=false)"
end
store->>prune: prune(loaded)
alt "MaxAge > 0"
prune->>prune: "cutoff = now() - MaxAge"
loop each session
alt "effectiveTime < cutoff"
prune->>disk: Remove(sessDir) [age eviction]
else
prune->>prune: add to survivors
end
end
end
alt "MaxCount > 0 and len(survivors) > MaxCount"
prune->>prune: sort survivors newest-first
loop sess in survivors[MaxCount:]
prune->>disk: Remove(sessDir) [count eviction]
end
prune->>prune: truncate to MaxCount
end
prune-->>store: survivors
store-->>main: []Session (pruned)
Reviews (3): Last reviewed commit: "fix(daemon): reject overflowing GMUX_SES..." | Re-trigger Greptile
A day count above ~106 751 overflows time.Duration (int64 ns) when multiplied by 24h, producing a negative MaxAge that prune silently treats as 'no age limit'. Cap at the largest safe value so an out-of-range setting is logged and ignored rather than quietly disabling retention. Addresses greptile review comment on PR #296.
A day count above ~106 751 overflows time.Duration (int64 ns) when multiplied by 24h, producing a negative MaxAge that prune silently treats as 'no age limit'. Cap at the largest safe value so an out-of-range setting is logged and ignored rather than quietly disabling retention. Addresses greptile review comment on PR #296.
4ec1678 to
7a18b00
Compare
Implements review ticket T22.
Dead-but-not-dismissed sessions previously persisted forever — each keeps a meta.json plus up to ~2 MiB of rotated scrollback under
~/.local/state/gmux/sessions/<id>/, removed only on explicit dismiss or orphan-sweep. For heavy users who never dismiss, the state dir grew unbounded. This adds a retention policy applied during the startupSweep: dead-session corpses older thanMaxAgeare aged out, then only the newestMaxCountsurvive (LRU by effective timestamp — ExitedAt, falling back to LastActivityAt then CreatedAt). Defaults are 30 days / 200 sessions, overridable viaGMUX_SESSION_RETENTION_DAYSandGMUX_SESSION_RETENTION_MAX(0 disables a limit).Safety rules:
Sweeponly loadsAlive=falserecords, and any still-running runner re-registers as alive immediately afterwards, so live sessions are never on the chopping block. Records with no parseable timestamp are treated conservatively — never aged out and ranked as newest for the count cap — so they are never evicted unexpectedly. The clock is injectable (WithClock) for deterministic tests; the policy is injected viaWithRetention, andNewwithout options preserves the prior no-prune behavior.Verification
cd services/gmuxd && go build ./...— passesgo test ./...— all packages pass, including new sessionmeta retention tests (age-out, count cap, no-policy, undatable-survives, env override)Source finding: L1