mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-24 18:19:05 +02:00
## Thinking Path
> - Paperclip is the open source app people use to manage AI agents for
work
> - #17248 and #17257 let git cache expiration remove closed issues'
worktrees after saving encrypted restore points
> - Restore points are kept for 90 days after an issue tree closes, then
deleted
> - Reopening a workspace whose restore point had expired failed, so the
issue could not get a workspace back
> - This pull request starts a fresh workspace on the same branch
instead, and warns that the saved work is gone
> - The benefit is that old issues can always be resumed, with a clear
record of what was lost
## Linked Issues or Issue Description
Refs #17248, #17257. Bug report described in-PR (bug report template).
**What happened?**
After `PAPERCLIP_GIT_RESTORE_POINT_RETENTION_DAYS` passes, the retention
worker marks the restore point expired and deletes it. Reopening that
workspace then fails with the git cache restoration error, every time.
**Expected behavior**
Reopening succeeds with a fresh workspace on the issue's branch, and the
issue says that unsaved work from before the cleanup is no longer
available.
**Steps to reproduce**
1. Enable restore points and let a closed issue's worktree expire with
unsaved work.
2. Let its restore point expire.
3. Reopen the issue: the workspace rebuild fails.
**Paperclip version or commit:** `master` at 650581b2f0.
**Deployment mode:** any.
**Agent adapter(s) involved:** not adapter-specific (core).
## What Changed
- **Detection:** `gitRestorePointExpired(recipe)` is true when the main
restore point or any nested checkout's restore point has `expiredAt`.
- **Reopen** (`execution-workspaces.ts`):
- `restoreGitCacheBase(..., { freshStart: true })` prepares the base
without the saved commit or the bundle.
- The saved recipe is left out of the metadata handed to the runtime,
and dropped from the stored metadata only when the reopen succeeds, so a
failed attempt can be retried and still warns. The runtime recreates the
worktree on its branch: the branch tip if the repository still has it,
otherwise a new branch from the base ref. When the expired recipe had
nested checkouts, the project's repositories are prepared with
`prepareProjectRepositoryWorkspaces` (without the expired saved state)
right after the worktree is recreated and before the provision command
runs, with the same copy identity and access rechecks as other reopen
copies.
- The result carries `warning`, the `execution_workspace.reopened`
activity records `restorePointExpired` and the warning, and a system
comment on the issue posts the warning in the same transaction.
- **Retention:** `markGitRestorePointExpired` now also marks nested
checkouts' restore points, matched by object key.
- **Docs:** `doc/recoverable-disk-cleanup.md` describes the fresh start.
No code comments were added.
## Verification
- `pnpm exec vitest run
src/__tests__/git-restore-point-expiration.test.ts` (in `server/`): 13
passed. New case: an expired restore point reopens fresh with the
warning and the system comment, on the original branch, even after the
base lost the branch and its unpushed commit; the recipe is gone. A
first attempt that fails keeps the expired recipe, and the retry still
warns and comments exactly once.
- New nested case in `git-workspace-expiration.test.ts`: an expired
nested checkout restore point is marked on the checkout entry and
reopens fresh with the warning, with the nested repository cloned again
at its pushed commit and without its expired uncommitted files.
- **Mutation checks:** each of these fails a case:
- removing the fresh base path (reopen fails);
- removing the system comment;
- handing the runtime the old recipe;
- dropping the stored recipe before the rebuild succeeds;
- marking only the main restore point expired;
- skipping nested repository preparation on a fresh start.
- `pnpm exec vitest run git-workspace-expiration git-workspace-contents
git-restore-point execution-workspaces` plus the environment selection
and heartbeat resume suites (in `server/`): 257 passed.
- `pnpm run typecheck` (in `server/`): pass.
- Not run: server build and boot smoke.
## Risks
- **Only with restore points enabled:** recipes get `expiredAt` only
from the restore point retention worker, so nothing changes otherwise.
- **Lost work is intended:** the fresh workspace has no uncommitted
files and, when the repository no longer has the branch, no unpushed
commits. The warning says so.
- **Recipe dropped:** after a fresh start the workspace no longer
carries the expired recipe, so later expirations start from a clean
slate.
## Model Used
- Claude Opus 5.5 (`claude-opus-5-5`), 1M context, through Claude Code
with tool use (shell, file edits, tests).
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [ ] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>