Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [0.10.2] - 2026-04-25

### Fixed

- **Cache write race on early TUI exit (regression in 0.10.1)** — v0.10.1 introduced streaming background scans; if the user exited the TUI before a worker finished, `cache::write_cache` persisted that agent as `(empty session list, fresh mtime)`, hiding its sessions on the next launch until file mtime changed again. `write_cache` now takes the still-scanning agent set and carries the prior cache entry verbatim for those agents — only completed scans replace cache state.

## [0.10.1] - 2026-04-25

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "agf"
version = "0.10.1"
version = "0.10.2"
edition = "2021"
description = "AI Agent Session Finder TUI — find, resume, and manage Claude Code, Codex, OpenCode, Gemini, Kiro, pi, and Cursor CLI sessions"
license = "MIT"
Expand Down
47 changes: 46 additions & 1 deletion src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,13 @@ pub fn load_cache() -> (Vec<Session>, Vec<Agent>) {
}

/// Write all sessions to cache, grouped by agent.
pub fn write_cache(sessions: &[Session]) {
///
/// `skip_agents` lists agents whose background scan did not complete this
/// run (e.g. the user exited the TUI before the worker finished); for those
/// we preserve the prior cache entry verbatim so we don't accidentally
/// persist an empty session list with a fresh `mtime`, which would mark the
/// agent "fresh" on the next launch and hide its sessions.
pub fn write_cache(sessions: &[Session], skip_agents: &std::collections::HashSet<Agent>) {
let path = cache_path();
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
Expand All @@ -192,10 +198,35 @@ pub fn write_cache(sessions: &[Session]) {
let plugins = plugin::all_plugins();
let mut agents: HashMap<String, AgentCache> = HashMap::new();

// Carry over prior cache entries for in-flight agents.
if !skip_agents.is_empty() {
if let Ok(content) = fs::read_to_string(&path) {
if let Ok(prior) = serde_json::from_str::<CacheFile>(&content) {
if prior.version == CACHE_VERSION {
for skip in skip_agents {
let key = agent_to_str(*skip).to_string();
if let Some(entry) = prior.agents.get(&key) {
agents.insert(
key,
AgentCache {
mtime: entry.mtime,
sessions: entry.sessions.iter().map(clone_cached).collect(),
},
);
}
}
}
}
}
}

for p in &plugins {
if !installed.contains(&p.agent()) {
continue;
}
if skip_agents.contains(&p.agent()) {
continue;
}
let key = agent_to_str(p.agent()).to_string();
let agent_sessions: Vec<CachedSession> = sessions
.iter()
Expand Down Expand Up @@ -225,6 +256,20 @@ pub fn write_cache(sessions: &[Session]) {
}
}

fn clone_cached(c: &CachedSession) -> CachedSession {
CachedSession {
agent: c.agent.clone(),
session_id: c.session_id.clone(),
project_name: c.project_name.clone(),
project_path: c.project_path.clone(),
summaries: c.summaries.clone(),
timestamp: c.timestamp,
git_branch: c.git_branch.clone(),
worktree: c.worktree.clone(),
recap: c.recap.clone(),
}
}

/// One agent's scan result, streamed back from a worker thread.
pub struct ScanResult {
pub agent: Agent,
Expand Down
4 changes: 3 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ fn main() -> anyhow::Result<()> {
let result = app.run()?;
// Persist whatever sessions accumulated during the TUI lifetime so
// the next launch reflects all scans that completed before exit.
cache::write_cache(&app.sessions);
// Agents still scanning at exit keep their prior cache entry so we
// don't accidentally persist "empty + fresh-mtime" for them.
cache::write_cache(&app.sessions, &app.scanning_agents);
result
};

Expand Down
Loading