Close the crash-window gap and exec-session leak a third review found
Secret Scan / scan (push) Successful in 16s
Build App (Preview) / compute-version (pull_request) Successful in 6s
Secret Scan / scan (pull_request) Successful in 6s
Build App (Preview) / create-release (pull_request) Successful in 3s
Build App (Preview) / build-macos (pull_request) Successful in 2m37s
Build App (Preview) / build-windows (pull_request) Successful in 4m52s
Build App (Preview) / build-linux (pull_request) Successful in 6m17s
Build App (Preview) / prune-previews (pull_request) Successful in 2s

A third Opus review pass confirmed round 2's fixes hold up, then found:

- The pending-cleanup record `remove_project` writes is fully durable
  (fsync'd); the projects_store.remove() that follows it is a plain
  fs::write with no fsync. A crash or power loss in that window — or that
  store write failing outright, beyond what the previous round's in-process
  rollback catches — leaves a record on disk naming a project
  projects.json still lists as present. The very next startup retry would
  then delete that project's container, snapshot image, and both volumes
  (including the one holding the OAuth credential and every session
  transcript) out from under a project the user still sees in the sidebar.
  retry_pending_cleanup_logged now takes the ProjectsStore and refuses to
  touch — clearing instead — any record whose project id still exists.
  Also stopped swallowing the round-2 rollback's own failure.
- Resolving the container through find_existing_container instead of
  project.container_id (round 2's stale-id fix) changed what drove
  close_sessions_for_container in remove_project and rebuild_project_
  container: sessions are now leaked when Docker is unreachable (nothing
  resolves, so nothing closes, and the project record is gone a moment
  later) and in the stale-id race itself (sessions were opened against the
  container that actually exists, not the id find_existing_container
  bypasses). Both functions now close sessions for the stored id
  unconditionally, and again for the resolved id if it differs.
- A pronoun-agreement bug in the no-retry removal toast ("remove them
  manually" for a single leftover) that was fixed one line above for verb
  agreement but not for the pronoun.

Also closed the test gaps the review named: the pending-cleanup
corrupt-record aside-move had no test, the Reset toast's leftover copy
was inline and untested (extracted to lib/resetOutcome.ts, mirroring
components/projects/home/removalReport.ts, with unit tests), and nothing
asserted rebuild()'s success path maps outcome.project into the list
rather than the whole outcome.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGjXq6fqtAFHdbhk4f3PfZ
This commit is contained in:
2026-08-27 09:47:45 -07:00
co-authored by Claude Sonnet 5
parent 439ef16f07
commit 61bdbc4a5b
9 changed files with 220 additions and 18 deletions
+8 -2
View File
@@ -250,6 +250,7 @@ pub fn run() {
// an image open and the sweep will not force; pins are untagged
// second so the images they were holding are dangling by the time
// the sweep lists them; the sweep runs last and collects both.
let projects_store_for_cleanup = projects_store_setup.clone();
tauri::async_runtime::spawn(async move {
crate::docker::reap_probe_containers().await;
let reaped = crate::docker::reap_stale_migration_pins().await;
@@ -259,8 +260,13 @@ pub fn run() {
crate::docker::sweep_orphaned_snapshots_logged("startup").await;
// A container/image/volume `remove_project` could not delete
// is recorded rather than lost — see triple-c#31 — and this is
// the only place anything ever retries it.
crate::commands::project_commands::retry_pending_cleanup_logged().await;
// the only place anything ever retries it. Takes the store so
// it can refuse to touch a project that turns out to still be
// live — see the long comment on the function itself.
crate::commands::project_commands::retry_pending_cleanup_logged(
&projects_store_for_cleanup,
)
.await;
});
// Auto-start web terminal server if enabled in settings