mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 11:01:16 +01:00
Close a lifecycle race between a reopen and the terminal-workspace reaper. A reopen publishes the workspace as active before the route changes the source issue out of the terminal state. In that window the reaper could observe a still-terminal source issue, archive the freshly rebuilt worktree, and delete it. The lifecycle-generation fence did not stop this, because the reaper made a new archive decision at the reopened generation. Add a reopen-pending flag in the workspace metadata. The reopen sets the flag in the same write that publishes the row as active. Both destructive paths exclude a flagged workspace, so neither one archives a worktree that a reopen rebuilt. - The reaper takes the per-workspace lifecycle lock before the archive write. The archive statement re-checks the status, the terminal predicates, and the reopen-pending flag under the lock, so it never archives a reopened row. - The reaper clears the flag on a later pass once the source issue leaves the terminal state, so a normal terminal cycle can reap the workspace again. - The archive route archives under the same lock through a new service method and clears the flag, so an explicit close cannot race a concurrent reopen. Co-authored-by: Paperclip <noreply@paperclip.ing>