Files
PaperClipAI/packages
Nicky LeachandPaperclip e926b13017 fix: keep sandbox termination progressing after bridge loss (#13287)
## Thinking Path

> - Paperclip must stop remote execution after losing its controller.
> - A bridge can remain blocked while the sandbox still incurs costs or
performs actions.
> - Waiting forever for that bridge prevents provider termination.
> - A temporary provider outage can also exhaust cleanup attempts
permanently.
> - This pull request bounds bridge drain and persists cleanup retries
with backoff.
> - Cleanup ends only after provider confirmation, without a user
accepting uncertain side effects.

## Linked Issues or Issue Description

Builds on merged #13285. Related #13254 added exact provider termination
receipts; merged #13272 adds explicit user retry. Merged #13352 stops
active sandbox startup before waiting for setup. This PR preserves that
immediate cancellation path and extends bounded teardown to ordinary
release and destroy. Cleanup continues automatically after repeated
provider failures. Refs #12953 for provider failures blocking execution.

**What happened?**
Daytona release waits for in-flight bridge activity before stop/delete.
A dead bridge can prevent that wait from finishing. The host also stops
cleanup after five failed attempts.

**Expected behavior**
Provider termination proceeds after a bounded bridge drain. Cleanup
retries survive service restarts and provider outages.

**Steps to reproduce**
Start a sandbox command whose bridge promise never resolves, then
release its lease. Separately, persist a pending-cleanup lease with five
failed attempts and recover the provider.

**Deployment mode**
Hosted Paperclip with a Daytona provider; rebased onto master at
`728f7185f` on September 14.

## What Changed

- Bound bridge drain and provider lifecycle calls. Prefer stop for
reusable sandboxes, with delete fallback.
- Persist cleanup attempt identity, renewable in-flight deadline, and
cooldown. Fence completion writes against superseded attempts.
- Preserve scoped explicit Retry and its activity log. Explicit Retry
can skip cooldown, but cannot take over a live cleanup attempt.
- Continue cleanup after five failures with slower retries and an
operator warning.
- Exclude leases in cooldown before paging so they do not starve due
work.
- Add hung-bridge, restart, provider-recovery, and concurrent-cleanup
regressions.

## Verification

- Rebased onto master at `728f7185f`. The outstanding diff contains only
cleanup changes; the merged controller-ownership prerequisite is
excluded.
- Daytona plugin suite: 160 passed, including immediate startup
cancellation, graceful release, hung activity, and teardown regressions.
- `pnpm exec vitest run
server/src/__tests__/heartbeat-pending-cleanup-sweep.test.ts`: 31
passed. Two added integration cases verify explicit Retry during
cooldown and while another cleanup owns the lease. They also verify run
scoping and the activity log.
- Targeted cleanup and cancellation cases in
`environment-runtime.test.ts`: 20 passed.
- Earlier live disposable Daytona test: provider stop ended background
work, resume preserved files without restarting the old process, a new
command succeeded, and the sandbox was deleted. This verifies provider
behavior; it was not repeated for this rebase.
- Latest-head CI and automated review are pending. Broad local tests,
typecheck, and build were not rerun for this focused rebase; CI supplies
those checks.

## Risks

- Timing out bridge drain permits provider termination; it never
supplies a stop receipt.
- A crashed cleanup attempt remains protected for 15 minutes, then
becomes eligible again. Repeated failures retry every 30 minutes after
escalation.
- The existing counter saturates at the escalation threshold; the new
attempt identity and deadline prevent overlapping claims.
- No schema, UI, telemetry, lockfile, or workflow change.

## Model Used

OpenAI GPT-6 through Codex, using reasoning, repository inspection, code
execution, and test tools. The precise backend revision and
context-window size are not exposed in this session.

## 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-17 10:01:57 -07:00
..