Repository navigation
fix(sandbox): stop concurrent reviews from evicting each other's checkouts - #1348
Merged
Merged
Maple Review Bot / Maple / review
succeeded
Oct 9, 2026 in 4m 51s
Confidence 8/10 · No issues found
🟢 Confidence 8/10 · likely safe to merge
Prune, timer and breaker semantics all check out by reading; only the container-side shell behaviour is unverified because verify:image needs Docker and was not run.
quality 100/100 · no findings · tests partial · risk medium · 2/2 new units observable
Stops concurrent sandbox reviews from evicting each other: eviction now goes by last use with a 15-minute grace, a dead clone's scratch directory is pruned by pid, one Worker call is capped, and a per-turn breaker refuses a sandbox that already failed. Contained; safe to merge once the container image check runs.
checkout.tsevicts by last use and keeps anything touched withinSANDBOX_CHECKOUT_GRACE_MINUTES- Clone scratch directories are named after the clone's pid and pruned only when that pid is gone
CloudflareRepoSandboxcaps one Worker call at the command's wall clock plusCALL_HEADROOMwithSandboxBreakerrefuses sandbox tools per repository for the rest of the turn
Before merge
- Manual · Run
bun run --cwd apps/sandbox verify:image(needs Docker): the clone and prune shell scripts changed and their container behaviour is unverified
What was checked
kill -0on the.clone-$$-XXXXXXpid matches the pidcloneRunnerrecords, and$$in the runner's subshell is the runner's own pid (checkout.ts:232,253,313)- A ready checkout is touched on every poll, and the prune skips a directory younger than the grace window regardless of count (
checkout.ts:251,291) - The breaker's executor is the one shared by the kickoff clone, every pass and the
review_fileschildren (turn-runner.ts:327); clone state lives under/var/lib/maple-clones, so the workspace prun…
Observability coverage: 2 of 2 changes observable
| Change | Kind | Observable | Evidence |
|---|---|---|---|
Sandbox call budget in CloudflareRepoSandbox poll |
outbound call | yes | Existing exec span keeps maple.sandbox.checkout_polls; the new cap fails with SandboxRunUnavailable, which reaches the model as an error tool result |
Per-turn sandbox breaker refusal (withSandboxBreaker.execute) |
in-turn tool gate | yes | The trip is logged with Logger.warn plus maple.mcp.tool log annotations; refusals surface as isError tool results in the session transcript |
95bc070 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.
Loading