Skip to content

Commit c505851

Browse files
icecrasher321claude
andcommitted
docs(sandboxes): correct the image cache's staleness invariant
Cursor Bugbot found that a released image can still be served from another replica's cache. The finding is real, and the reason it went unnoticed is that the cache documented an invariant which eager release quietly broke. It claimed a `ready` row is terminal for its spec hash, so a cached hit could not go stale in a way that matters. That held while the only ways a row changed were an edit (new hash) or a delete (caught by the `workspace_sandbox` read). Releasing an image eagerly made a `ready` row disappear with the hash unchanged, so the premise no longer holds and the comment was actively misleading to the next reader. No behaviour change here — the exposure is bounded at IMAGE_TTL_MS on replicas other than the one that ran the release, and it self-heals once the entry expires and the row read finds nothing. Closing it properly needs cross-replica invalidation or a provider-error path that invalidates on "template not found", both of which are larger than a review fix; the comment now says so instead of implying the problem cannot exist. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 081485c commit c505851

1 file changed

Lines changed: 15 additions & 5 deletions

File tree

apps/sim/lib/execution/remote-sandbox/resolve.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,21 @@ export async function resolveWorkspaceSandbox(args: {
223223
/**
224224
* Reads a build row, memoized on its content address.
225225
*
226-
* A `ready` row is terminal for that spec hash, so caching it cannot go stale in
227-
* a way that matters — editing a sandbox produces a different hash, and deleting
228-
* one is caught by the `workspace_sandbox` read that always runs. A non-ready
229-
* row is NOT cached: it is precisely the value that flips underneath us while a
230-
* build completes, and caching it would keep a just-finished build unusable.
226+
* Editing a sandbox produces a different hash and deleting one is caught by the
227+
* `workspace_sandbox` read that always runs, so those cannot serve a stale hit. A
228+
* non-ready row is NOT cached either: it is precisely the value that flips
229+
* underneath us while a build completes, and caching it would keep a just-finished
230+
* build unusable.
231+
*
232+
* A `ready` row is no longer strictly terminal, though, and this cache is
233+
* per-process. `releaseSandboxImage` clears only the replica that ran it, so
234+
* another replica can serve a cached `ready` image for up to {@link IMAGE_TTL_MS}
235+
* after its template was deleted — and because the hit looks healthy, resolution
236+
* hands back a dead `imageRef` instead of reaching the repair path. Sandbox
237+
* creation then fails on that replica until the entry expires and the row read
238+
* finds nothing. Bounded and self-healing, but real; closing it needs either
239+
* cross-replica invalidation or a provider-error path that invalidates on
240+
* "template not found".
231241
*/
232242
async function readImage(providerId: string, specHash: string): Promise<CachedImage | undefined> {
233243
const cacheKey = `${providerId}:${specHash}`

0 commit comments

Comments
 (0)