mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-14 17:22:00 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A native run on a reused warm sandbox first retires the previous run's warm session, and that retirement collects the old session's agent files > - On a server without a persistent volume, agent-file collection takes a per-agent Postgres lock, so runs of one agent collect one at a time > - #20863 sends a run whose predecessor cannot be retired to a fresh sandbox. It classified every retirement error as unprovable, including a busy lock held by another run of the same agent > - So ordinary lock contention failed the run, dropped a healthy sandbox and rested the task > - This pull request makes retirement wait for a busy directory lock in the same run, and keeps the fresh-sandbox fallback for retirements that stay unprovable after the lock is obtained > - The benefit is that busy agents no longer fail follow-up runs on their warm sandboxes ## Linked Issues or Issue Description **What happened?** A no-fault restart-chaos wave with 8 concurrent runs of one Claude agent on Daytona, with a volume-free server, failed one monitor-due follow-up after 6 seconds. The error was `adapter_failed`, cause `native_predecessor_retirement_unproven`: "The previous run's session on this sandbox could not be retired (Agent directory operation is already active)". The run also logged `INSTRUCTION_COLLECTION_UNAVAILABLE`. The stack shows the error came from `claimWarmNativeInstructionCopy` during instruction preparation. The previous session had closed; only its agent-file collection met the per-agent lock that another run held. **Expected behavior** A busy agent-directory lock is transient. The run waits for it and then retires the predecessor on the same sandbox. Only a retirement that stays unprovable after the lock is obtained takes the fresh-sandbox fallback. **Steps to reproduce** 1. Run several warm native tasks of one agent at once on Daytona with a volume-free server. 2. Re-wake one task while another run of the agent collects its files. 3. The follow-up fails with `native_predecessor_retirement_unproven` and its sandbox is dropped. Refs #20863, #20829, #20957. ## What Changed - `retireWarmPredecessor` (native executor): a busy agent-directory lock (`AgentDirectoryOperationBusyError`) is waited out in the same run, polling every second for up to five minutes (`warmRetirementDirectoryWait`). The usual holder is another run's short collection, so retirement then finishes on the same sandbox with the copy this run adopted. The run is not deferred: deferring after adoption would release the adopted copy and could skip saving the predecessor's edits. - Past that budget the run fails with its own error, `native_predecessor_retirement_directory_busy`, not as an unprovable retirement. Before failing it files the adopted copy's retirement receipt, so the copy is kept as `pending_collection` and run cleanup does not mark it `unavailable`. The owner is not marked unretired and stays registered for its sandbox, so a later run retires it there. - An idle-timeout close that meets the lock retries after the agent-directory retry delay instead of leaving the session registered with no retry. - Closing idle sessions for environment delete (#20957) reports such an owner as `busy`, not `failed`. ## Verification - RED on master, then GREEN: - `native-session-executor.test.ts -t "directory lock"`: two runs of one agent contend on the lock while a follow-up retires the predecessor. On master the claim rejects with `NativePredecessorRetirementUnprovenError` (`Agent directory operation is already active`). Now a short-lived lock is waited out and the predecessor is retired and collected on the same sandbox; a lock that outlasts the budget fails with `native_predecessor_retirement_directory_busy`, environment delete reports `busy: 1`, a fresh-sandbox attempt does not forget the owner, and once the lock is free a later run retires it. - `native-session-executor.test.ts -t "idle close whose file collection"`: on master the idle close never retries; now it retries and closes. - Existing suites pass: native executor, agent-directory busy heartbeat, native pre-dispatch retry, native controller recovery and plugin runner eviction. Server `tsc --noEmit` passes. ## Risks - Under contention a follow-up can wait up to five minutes before its first turn. The normal path adds no wait. - A lock held for more than five minutes fails the run visibly instead of dropping the sandbox. - An idle owner whose collection stays busy keeps retrying at the 5 to 15 second agent-directory delay until the lock frees, a controller restart, or the next admission. - No persisted state changes. ## Model Used - Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, agentic coding with tool use (shell, file edits, test runs) in Claude Code. ## 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 - [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>