Files
PaperClipAI/packages/plugins/sandbox-providers
DottaandClaude Opus 5.5 367aa16800 fix(daytona): drop the stale session id on every sandbox restart (#19050)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Native agent runs can execute inside a Daytona sandbox through the
Daytona sandbox provider plugin
> - The plugin runs every command for a lease in one persistent Daytona
process session and keeps that session id in memory
> - Daytona auto-stops an idle sandbox, and a stopped sandbox loses all
of its process sessions
> - Only the execute and resume hooks dropped the stored session id
before a restart. Workspace realize, sync-in, sync-out, runner ingress,
and interactive setup also restart a stopped sandbox, but they kept the
dead id
> - When host-side workspace preparation stays idle for longer than the
auto-stop interval, sync-in restarts the sandbox, and the next command
sends the dead id. The native run then fails with
`native_session_interrupted: session not found`
> - This pull request moves the session-id clear into the one shared
start helper, so every restart path drops the stale id
> - The benefit is that a run that waits through a long preparation
opens a fresh session instead of failing

## Linked Issues or Issue Description

**What happened?**
Native runs on Daytona failed with `native_session_interrupted` and the
error `session not found`. The failure came about 4 seconds after the
provider session started. In every observed run, the workspace pack
phase took longer than 15 minutes (917 s to 1442 s). This is longer than
the default Daytona `autoStopInterval` of 15 minutes.

**Expected behavior**
After Daytona restarts an auto-stopped sandbox, the next command opens a
new process session and the run continues.

**Steps to reproduce**
1. Run one command on a Daytona lease, so the plugin opens and stores a
session.
2. Let the sandbox stop (Daytona auto-stop after an idle gap).
3. Call workspace realize or sync-in on the lease. The hook restarts the
sandbox.
4. Run the next command. It uses the stored session id, and Daytona
answers `session not found`.

No related open PR exists. The undeployed native fixes on master
(#18992, #19025, #19026, #19004) do not touch the Daytona session store.

## What Changed

- `ensureSandboxStarted` in the Daytona plugin now takes the lease
scope. When the sandbox is not running, it clears the stored session id
before it starts or recovers the sandbox.
- All seven callers (execute, resume, realize workspace, interactive
setup, runner ingress, sync-in, sync-out) pass their lease scope. The
compiler enforces the scope on any new caller.
- The execute hook no longer has its own copy of the clear. The resume
hook keeps its clear because it also closes duplex channels before the
restart.
- Added three regression tests: a fresh session after realize restarts
an auto-stopped sandbox, a fresh session after sync-in restarts it, and
no new session when realize finds the sandbox already running.

## Verification

- Red before the fix: `pnpm exec vitest run src/plugin.test.ts -t
"auto-stopped sandbox|already running"` in
`packages/plugins/sandbox-providers/daytona`. The two restart tests
failed with `expected "vi.fn()" to be called 2 times, but got 1 times`.
The running-sandbox safety test passed.
- Green after the fix: the full Daytona plugin suite (`pnpm exec vitest
run`) passes, 542 passed and 8 skipped.
- `pnpm typecheck` in the Daytona plugin package passes.
- Not done: a live Daytona run that idles past the auto-stop interval.
The fix needs deployment before the staging failure can be checked
again.

## Risks

- Low risk. The clear runs only when the sandbox is not in the `started`
state. A stopped sandbox has no live session, so no live shell can leak.
A running sandbox keeps its stored session, and a test covers this case.
- The recover path for a recoverable `error` state now also drops the
id. A recovered sandbox restarts, so its old session is also gone.
- Duplex channels on a restarted sandbox are still closed only by the
resume hook. This PR does not change that behavior.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, used through Claude
Code with tool use (shell, file edits, browser reads of staging run
records).

## 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:24:41 -06:00
..