Commit Graph
2 Commits
Author SHA1 Message Date
shadow-testandClaude Sonnet 5 61bdbc4a5b 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
2026-08-27 09:47:45 -07:00
shadow-testandClaude Opus 5 1768240861 Unstick a project row whose lifecycle command was refused
Two more pieces of drift from the same merges, both invisible to `tsc`.

**A refused Start/Stop/Reset strands the row.** `start`, `stop` and
`rebuild` paint an optimistic "starting"/"stopping" so a click moves the
row at once. That was safe while the only way these could fail was after
the backend had begun changing things. `fix/sec`'s per-project lock ended
that: all three now take the lock and are refused *before* any state
changes, and `stop` could not fail this way at all before — it took no
exclusion. So the optimistic paint has nothing to become, `isTransitioning`
disables both Start and Stop, and the only thing that clears it is
`reconcileProjectStatuses`, which runs once from `App.tsx` when Docker
first appears. Clicking Stop during a compaction left the project
unusable until the app was restarted.

`withOptimisticStatus` re-reads the authoritative list when the command
throws, and falls back to the status that was on screen if even that call
fails — two failures in a row must not land on the one state there is no
way out of. The error is rethrown unchanged, so the toast is unaffected.
Five of the six new tests fail against the previous code; the sixth pins
that the optimistic paint still happens on the way in.

**Six secrets are typed as if they arrive, and they never do.**
`git_token`, the four Bedrock credentials and `OpenAiCompatibleConfig
.api_key` are `#[serde(skip_serializing)]` in Rust, so the key is absent
from every project the backend returns — reading one gives `undefined`,
not the `null` the type promised. Every current reader happens to use
`?? ""`, so nothing is broken today; a single `=== null` would have been a
branch that silently never ran. They are optional now, which makes that a
compile error, and documented as write-only, which is what they are.

669 frontend tests pass, `tsc --noEmit` clean, `npm run build` green.
Nothing under `src-tauri/` touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBq2rGum6GX7xXgsas1fDc
2026-08-23 12:12:50 -07:00