Repository navigation
fix(macapp): tombstone a project while Delete Project tears it down - #294
Conversation
The session store writes on its own serial queue, so a fixed 0.2-0.3s run-loop pump before asserting an expected write flaked under a loaded parallel test run (reproduced 1 in 3 under CPU load; 0 in 9 after). Expected writes now pump until they land, capped at 5s. Assertions that nothing is written keep their fixed pumps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting a project with remote workrooms can take minutes, and nothing stopped a reload in that window from republishing the project with half-destroyed workrooms, which the user could then open or delete again. Delete Project now puts the project's path in `deletingProjects`, the project-level twin of `deletingWorkrooms`, held in the shared ProjectStore so every window's reload drops it. While it is set, a second delete, a local create and a remote create in that project are refused. On success the app trashes (--from-disk), then begins a fresh quiet load before lifting the tombstone, so a read issued before config dropped the project loses publication to the newer generation. On failure it lifts the tombstone before reloading, so the project, still in config, reappears. Fixes #287. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 19 minutes. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (4)
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (6)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughProject deletion now uses a shared tombstone to exclude projects from reload results and block creation or duplicate deletion during teardown. The app reloads before clearing the tombstone on success and clears it before reloading after failure. Session-save tests now wait for expected asynchronous states. ChangesProject deletion tombstones
Session-save test waits
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant AppStore
participant ProjectStore
participant CLI
AppStore->>ProjectStore: Add project path to deletingProjects
AppStore->>CLI: deleteProject
CLI-->>AppStore: Teardown result
AppStore->>AppStore: Reload while tombstone remains set
AppStore->>ProjectStore: Clear tombstone after successful reload
Merge Risk: ⚪ Minimal · up to No actionable issue remains that should prevent merging after normal checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change strengthens protection against using or deleting a project while its teardown is running. No introduced security vulnerability was established. Interrupted teardown and concurrent re-registration remain incompletely validated, so some lifecycle risk remains. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Out of Scope Changes checkExplanation The Full details: Docstring CoverageExplanation Docstring coverage is 78.26% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 5 files. (1 skipped: 1 unsupported.) ✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The once-per-launch sweep removes labelled hosts that no recorded host names. A reload during Delete Project or Delete Workroom hands `adopt` a tombstone-filtered list, so if that reload ran the launch's first sweep it treated the deleting item's hosts as unknown, and a delete that then failed left config recording hosts the sweep had destroyed. `adopt` now takes `sweep:`, false while any project or workroom tombstone is set, and `claimSweep(allowed:)` leaves a disallowed call's sweep for the next one. Also pins `--from-disk`'s order: the Bin move happens while the project is still tombstoned and before the success path's reload begins. Fixes #296. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload during a delete now holds back the launch's sweep (#296), but a successful delete only lifts its tombstone (Delete Project after its own still-tombstoned reload, Delete Workroom with no reload), so the held sweep waited for some later reload. Both success paths now call `adopt` once no tombstone is left, which runs it. A failed delete's reload already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`runHeldHostSweep` adopted from the in-memory `projects`, which can still hide a host config records: two overlapping remote workroom deletes hide both, and if one fails and its recovery reload fails too, the other's success swept against that stale list. It now does a quiet `load` instead, only while a sweep is still owed (`sweepPending`), so `apply` sweeps from a fresh `list`, or holds it again if another delete has begun. A failed `list` applies nothing, so the sweep waits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`sweepPending` stayed true for a launch whose `adopt` never reached the sweep (nothing recorded), so with remote previews on every successful delete ran an extra quiet `load`. `sweepHeld` is true only once a call reached the sweep and held it, which happens at most once per launch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reload whose `list` ran before the teardown wrote config could still publish the workroom once `deletingWorkrooms` dropped it: nothing newer had begun, so it won `claimPublication`. The success path now runs a quiet load first, as Delete Project does since #294, so the stale read is superseded. RemoteWorkroomDeletionTests' fake now lists what config would hold after the delete, since that reload publishes it. Fixes #295 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Fixes #287. Fixes #296.
Delete Project now hides the project until its teardown ends. Tearing down a project's remote workrooms can take minutes. Before this change, any reload in that window, such as another window's create or a failed delete, republished the project with half-destroyed workrooms. The user could then open panes on it or start a second delete.
deletingProjects, the project-level twin ofdeletingWorkrooms. It lives in the sharedProjectStore, so every window's reload drops it.--from-diskmoves the returned directories to the Bin, the app begins a fresh, quiet load, and only then lifts the tombstone. Alistissued before config dropped the project therefore losesclaimPublicationto the newer generation and can't republish it. A failedlistthere doesn't report the successful delete as an error.Separate commit: a flaky test fixed.
SessionSaveTestswaited a fixed 0.2–0.3s for writes that land on the store's own queue. Two of its tests flaked during this work. Under CPU load (oneyesper core) the old code failed 1 run in 3; the new code passed 9 of 9. Tests that expect a write now pump until it lands, capped at 5s. Tests asserting that nothing is written keep their fixed pumps.Holds the remote-host sweep while a delete is in flight (#296). This was raised in review. A reload during Delete Project or Delete Workroom hands
RemoteHosts.adopta tombstone-filtered list. If that reload ran the launch's one sweep, the sweep treated the deleting item's hosts as unknown and could destroy them. A delete that then failed left config recording hosts that no longer existed.adoptnow takessweep:, false while any tombstone is set.claimSweep(allowed:)leaves the sweep for the next call.Test Coverage
New tests are in
AppStoreDeleteRaceTests, plus one assertion added toWorkroomStatusTests:AppStores on oneProjectStore.prepareRepositories) across a successful delete doesn't republish the project.canCreateRemoteWorkroomis false.Each guard was mutation-checked: breaking it turns its test red. The checks covered removing the reload filter, clearing the tombstone after (not before) the failure reload, keeping the tombstone per window, inserting it before the refusal, dropping the success-path reload, and removing each create guard.
Coverage: 73% value-weighted at the first audit (91% any test). The gaps it named are now tested: cross-window sharing, the early-return ordering and the failure surfacing.
Known, not tested:
list.Pre-Landing Review
There were five passes of checklist and specialist review (testing and maintainability, plus simplification once the diff passed 100 lines). Security, performance, migration, API and design were out of scope.
Fixed during review:
Skipped, with reasons:
stores, which holds every window'sAppStorestrongly.pump(until:)waits vacuously": false positive.lastWrittenis set synchronously before the write. Breaking the failure latch fails 4 tests.Adversarial review: three rounds, each with Claude and Codex (
gpt-6-astra), then a final focused Codex pass, which reportedNO_FINDINGS. Fixed from those rounds:--from-disknow trashes before the reload suspends.load.Accepted, matching existing behaviour:
Follow-ups (not in this PR)
startWorkroomTeardownliftsdeletingWorkroomsstraight aftercli.deletereturns, with no newer load. The same reload-before-lift fix applies.Exploratory QA
App-level behaviour is covered by the unit tests above, which drive
AppStorethrough a gated fake CLI and a gatedprepareRepositories. Not run: a manual Delete Project against real remote hosts, andmake app-uitest.Design Review
No frontend files changed, so design review was skipped.
Eval Results
No prompt-related files changed, so evals were skipped.
Scope Drift
Scope Check: CLEAN. The fix is confined to
deleteProject, the reload filter and the two create guards. TheSessionSaveTestscommit is a separately approved fix for a pre-existing flake found while running the suite.Plan Completion
No plan file detected. The work is defined by #287.
Verification Results
No plan-specific verification items. The automated checks are under Test plan.
TODOS
No TODO items completed in this PR.
Documentation
Status: updated. One factual edit, no blockers.
docs/designs/remote-workrooms.mdgets an "As built (#253, part 4: deleting)" bullet for #287. It covers thedeletingProjectstombstone, the refusals, and the order of reload and lift on success and on failure. The status paragraph's "follow-ups #283 to #288" sentence is unchanged.README.md,AGENTS.md,CONTRIBUTING.md,macapp/AGENTS.md,macapp/README.mdandTODOS.mdwere reviewed and need no change. No coverage debt: there is no new public surface.Test plan
make app-lint: cleanmake app-test: 3038/3038 passed on the final code (the only later change is the design doc)make app-test -only-testing:WorkroomAppTests/SessionSaveTestsunder CPU load: 9/9 passed (old code: 1 failure in 3)make app-uitest: not run🤖 Generated with Claude Code
Summary by CodeRabbit