Skip to content
32 changes: 21 additions & 11 deletions src/commands/up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2113,7 +2113,7 @@ async fn delete_project_inner(state: AppState, id: String, query: DeleteProjectQ
// Row deletion can wait up to RUN_ARTIFACTS_WAIT for the run-artifacts lock, so keep it off the async workers.
tokio::task::spawn_blocking(move || {
let native_purge = (!session_ids.is_empty()).then_some(|| {
local::chat::ChatHost::delete_native_sessions_blocking(&store, &session_ids)
local::chat::ChatHost::purge_native_history_blocking(&store, &session_ids)
});
store.delete_local_project_with(&project_id, native_purge)
})
Expand Down Expand Up @@ -4656,7 +4656,7 @@ async fn reclaim_orphaned_run_artifacts(
) {
// Demo prewarm skips itself when the gate is contended at startup.
tokio::time::sleep(Duration::from_secs(5)).await;
// Orphans whose failure is already logged, so a stuck one logs once.
// Orphans and sweep failures already logged, so a stuck one logs once.
let mut reported = HashSet::new();
loop {
let mut more = false;
Expand All @@ -4683,16 +4683,26 @@ async fn reclaim_orphaned_run_artifacts(
eprintln!("orx up: orphaned run artifact cleanup failed: {error}")
}
}
match tokio::task::spawn_blocking(|| {
Store::open()?.cleanup_orphaned_chat_attachments()
})
.await
{
Ok(Ok(left)) => more |= left,
Ok(Err(error)) => {
eprintln!("orx up: could not clean orphaned chat attachments: {error}")
let sweeps = [
(
"chat attachments",
Store::cleanup_orphaned_chat_attachments as fn(&Store) -> Result<bool>,
),
(
"native chat history",
Store::cleanup_orphaned_native_sessions,
),
];
for (label, sweep) in sweeps {
match tokio::task::spawn_blocking(move || sweep(&Store::open()?)).await {
Ok(Ok(left)) => more |= left,
Ok(Err(error)) => {
if reported.insert(format!("{label}: {error}")) {
eprintln!("orx up: could not clean orphaned {label}: {error}");
}
}
Err(error) => eprintln!("orx up: orphaned {label} cleanup failed: {error}"),
}
Err(error) => eprintln!("orx up: chat attachment cleanup failed: {error}"),
}
}
}
Expand Down
20 changes: 14 additions & 6 deletions src/local/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6733,7 +6733,7 @@ impl ChatHost {
let native_deletion = store.lock_native_deletion()?;
let session = store.get_chat_session(session_id)?;
let worktree_owner = store.chat_worktree_owner(session_id)?;
Self::delete_native_sessions(&[session_id.to_string()]).await?;
Self::purge_native_history(&[session_id.to_string()]).await?;
store.delete_chat_session(session_id)?;
drop(native_deletion);
self.claude.forget_session(session_id).await;
Expand All @@ -6750,17 +6750,25 @@ impl ChatHost {
Ok(())
}

pub async fn delete_native_sessions(session_ids: &[String]) -> Result<()> {
pub async fn purge_native_history(session_ids: &[String]) -> Result<()> {
let session_ids = session_ids.to_vec();
tokio::task::spawn_blocking(move || {
Self::delete_native_sessions_blocking(&Store::open()?, &session_ids)
Self::purge_native_history_blocking(&Store::open()?, &session_ids)
})
.await?
}

pub fn delete_native_sessions_blocking(store: &Store, session_ids: &[String]) -> Result<()> {
let (ids, retained) = store.opencode_sessions_for_deletion(session_ids)?;
crate::local::native_store::delete_opencode_sessions(&ids, &retained)
pub fn purge_native_history_blocking(store: &Store, session_ids: &[String]) -> Result<()> {
let (ids, retained) = store.native_sessions_for_deletion(session_ids, &["opencode"])?;
crate::local::native_store::delete_opencode_sessions(&ids, &retained)?;
// ACP agents keep history in homes orx does not own, so it is left alone.
// Survivors span every harness: a Codex sub-agent may be recorded by any other chat.
let (ids, _) =
store.native_sessions_for_deletion(session_ids, &["claude-code", "codex", "cursor"])?;
crate::local::native_store::delete_isolated_sessions(
&ids,
&store.native_ids_outside(session_ids)?,
)
}
}

Expand Down
Loading
Loading