Skip to content

fix(sandbox): stop concurrent reviews from evicting each other's checkouts - #1348

Merged
Makisuo merged 3 commits into
mainfrom
fix/sandbox-concurrent-checkouts
Oct 9, 2026
Merged

Makisuo merged 3 commits into
mainfrom
fix/sandbox-concurrent-checkouts

Merge origin/main into fix/sandbox-concurrent-checkouts

95bc070
Select commit
Loading
Failed to load commit list.
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.ts evicts by last use and keeps anything touched within SANDBOX_CHECKOUT_GRACE_MINUTES
  • Clone scratch directories are named after the clone's pid and pruned only when that pid is gone
  • CloudflareRepoSandbox caps one Worker call at the command's wall clock plus CALL_HEADROOM
  • withSandboxBreaker refuses 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 -0 on the .clone-$$-XXXXXX pid matches the pid cloneRunner records, 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_files children (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.