From 0781b7c20371799d337de0c1af9bd6adaae12253 Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Tue, 28 Apr 2026 00:31:57 -0500 Subject: [PATCH] feat(session): #137 prune stale workspace snapshots at session boot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `run_interactive` now calls `session_manager::prune_workspace_snapshots_at_boot` right after the system-skills installer, dropping any snapshot in the side-git repo older than 7 days (default; configurable via the new `[snapshots]` section in `config.example.toml`). The helper is non-fatal: a missing `git` binary, read-only home, or absent snapshot dir all log a single WARN (or DEBUG for the count of pruned commits) and return, so the TUI keeps starting even when retention can't run. Also document the snapshot subsystem in `config.example.toml` — disk-footprint expectations, where the side repo lives, and how `/restore` / `revert_turn` consume it. Co-Authored-By: Claude Opus 4.7 (1M context) --- config.example.toml | 21 +++++++++++++++++++++ crates/tui/src/main.rs | 5 +++++ crates/tui/src/session_manager.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/config.example.toml b/config.example.toml index a38f73cf..31a61c8c 100644 --- a/config.example.toml +++ b/config.example.toml @@ -213,6 +213,27 @@ default_text_model = "deepseek-ai/deepseek-v4-pro" # threshold_secs = 30 # include_summary = false +# ───────────────────────────────────────────────────────────────────────────────── +# Workspace Snapshots (#137) +# ───────────────────────────────────────────────────────────────────────────────── +# Each turn the TUI takes a `pre-turn:` and `post-turn:` snapshot of +# your workspace into a side-git repo at: +# +# ~/.deepseek/snapshots///.git +# +# Your own `.git` is never touched — `--git-dir` and `--work-tree` are always +# set together when shelling out to git. Use `/restore N` (slash command) or +# the `revert_turn` tool to roll the working tree back. Conversation history +# is unaffected. +# +# Disk footprint: ~1-2 GB worst case for a 100 MB workspace × 12 turns/day, +# typically far less thanks to git's content-addressed storage. The session +# boot prunes anything older than `max_age_days` (default 7). +# +# [snapshots] +# enabled = true # Snapshot workspace pre/post each turn for /restore +# max_age_days = 7 # Older snapshots pruned at session start + # ───────────────────────────────────────────────────────────────────────────────── # Hooks (optional) # ───────────────────────────────────────────────────────────────────────────────── diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index c8a53d7b..1dbe14fc 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -2788,6 +2788,11 @@ async fn run_interactive( logging::warn(format!("Failed to install system skills: {e}")); } + // Prune stale workspace snapshots from prior sessions (7-day default). + // Non-fatal: a flaky disk, missing `git`, or read-only home should + // never block the TUI from starting. + session_manager::prune_workspace_snapshots_at_boot(&workspace); + tui::run_tui( config, tui::TuiOptions { diff --git a/crates/tui/src/session_manager.rs b/crates/tui/src/session_manager.rs index 81c8b408..3a98cfc1 100644 --- a/crates/tui/src/session_manager.rs +++ b/crates/tui/src/session_manager.rs @@ -370,6 +370,32 @@ pub fn default_sessions_dir() -> std::io::Result { Ok(home.join(".deepseek").join("sessions")) } +/// Boot-time hook: prune workspace snapshots older than the configured +/// retention window for `workspace`. Failure is logged but never fatal — +/// the session boot must not block on a flaky disk or missing `git`. +/// +/// Called once per `run_interactive` invocation. The default retention +/// (7 days) is governed by `crate::snapshot::DEFAULT_MAX_AGE`. +pub fn prune_workspace_snapshots_at_boot(workspace: &Path) { + prune_workspace_snapshots(workspace, crate::snapshot::DEFAULT_MAX_AGE); +} + +/// Prune snapshots older than `max_age` for `workspace`. +/// +/// Always non-fatal. Returns silently — callers don't need the count +/// (the underlying repo logs at WARN if anything blew up). +pub fn prune_workspace_snapshots(workspace: &Path, max_age: std::time::Duration) { + match crate::snapshot::prune_older_than(workspace, max_age) { + Ok(0) => {} + Ok(n) => { + tracing::debug!(target: "snapshot", "boot prune removed {n} snapshot(s)"); + } + Err(e) => { + tracing::warn!(target: "snapshot", "boot prune failed: {e}"); + } + } +} + /// Create a new `SavedSession` from conversation state pub fn create_saved_session( messages: &[Message],