Repository navigation
fix(task): name a cancelled sibling's sandbox in the retry marker - #511
Merged
Merged
Conversation
A deploy step's record reaches the store only through its completion write. When a sibling's retry undo lands first, that write is fenced, the retry_resets marker misses the sandbox, and teardown_run leaks it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The retry_resets marker now names each sandbox id that a record in the stored or live context before an undo names and no replayed record does, trimming an env record to those ids, so a fenced sandbox reaches teardown_run and a live one is never listed. The model gains NoSandboxLeaks and NoMarkerNamesLive. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
andrewpark-scaleai
enabled auto-merge (squash)
November 6, 2026 00:16
earakely-scale
approved these changes
Nov 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
A retry could lose track of a sandbox, so
teardown_runnever terminated it and it ran until the provider's own timeout. When a retryable step fails,_drain_in_flightcancels its in-flight siblings. A sibling that had created a sandbox and appended its record to the live context, but whose completion write had not landed yet, lost that record: theretry_resetsmarker was built from the stored context only, and_rebuild_contextthen replaced the live one.recorded_sandboxesreads only those two places, so nothing named the sandbox._rolled_back_sandboxesnow also reads the live context that the rebuild replaces, captured in_roll_back_spanbefore the undo.recorded_sandboxesuses, instead of whole records, and lists only the ids that no surviving record names. Before, a surviving record that serialized differently in memory and in the store, such as an older stored shape or a datetime in metadata, was named as rolled back, and an env whose ids overlapped a surviving env's (the gateway builds them from shared provider state) put ids still in use into the marker. Mixed-type dict keys no longer raise inside the undo.teardown.env_record_idsis the one rule for which ids an env record names, shared by teardown and the marker. It ignores asandbox_idsvalue that is neither an id nor a{service: id}map, where teardown used to raise.tst/formal/step_journalgains ghost sandbox state and two invariants: every sandbox a run creates is recorded when the run ends (NoSandboxLeaks), and the marker never names a sandbox that is still recorded as deployed (NoMarkerNamesLive). The model found the leak before the real-code test reproduced it.How it was tested
tst/unit/task/retry_sandbox_teardown_test.pyruns the realDeploySandboxTaskStep,Task.runand the SQLite store, and holds the sibling's completion write until the undo lands. Before the fix it fails withassert ['sb-2'] == ['sb-1', 'sb-2']. The first commit adds it as a strict xfail. It also asserts the marker names only the rolled-back sandbox.tst/unit/task_step/retry_test.pyandtst/unit/task/teardown_test.pycover record shapes that differ between memory and store, overlapping env ids, and malformedsandbox_idsvalues. Each fails on the previous code and passes on the fix.tst/formal/step_journal/check.sh:singleandtakeoverhold.check.sh mutations: every line ok, includingMarkerFromStoreOnly(the old code) breakingNoSandboxLeaksandMarkerNamesSurvivorsbreakingNoMarkerNamesLive.pytest tst/unit/task tst/unit/task_step tst/unit/runner tst/unit/cli: 3037 passed.make lintpasses.

Confidence Score: 5/5
The PR appears safe to merge; no actionable issues were found.Summary
Keeps sandboxes from cancelled sibling steps discoverable after a retry rebuilds the context.
No actionable issues found. Tests and model checks were not run during this review.
Diagram
sequenceDiagram participant S as Sibling deploy step participant T as Task participant D as Store participant C as Teardown S->>T: Append sandbox to live context S->>D: Start completion write T->>S: Cancel and await sibling T->>T: Snapshot live context T->>D: Undo retry span D->>D: Mark removed IDs from stored and live records D-->>T: Rebuilt context with retry marker T->>C: Finished context C->>C: Find live and rolled-back sandboxesReviews (1) · Last reviewed commit: "fix(task): name a cancelled sibling's sa..." · Reviewed by Greptile