{"record":{"id":"2deca77ef3af053d","repo":"paperclipai/paperclip","slug":"createos-sandbox-did-not-reach-desired","errorCode":null,"errorMessage":"CreateOS sandbox did not reach ${desired}.","messagePattern":"CreateOS sandbox did not reach (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/client.ts","lineNumber":126,"sourceCode":"  async transition(id: string, desired: \"running\" | \"paused\", signal: AbortSignal): Promise<void> {\n    let submitted = false;\n    for (;;) {\n      signal.throwIfAborted();\n      const sandbox = await this.getSandbox(id, signal);\n      if (sandbox.status === desired) return;\n      const canSubmit = desired === \"running\"\n        ? [\"paused\", \"error\"].includes(sandbox.status!)\n        : sandbox.status === \"running\";\n      if (canSubmit && !submitted) {\n        try {\n          await this.json(`/sandboxes/${id}/${desired === \"running\" ? \"resume\" : \"pause\"}`, \"POST\", undefined, signal);\n          submitted = true;\n        } catch (error) {\n          // A concurrent state transition is reconciled by reading its state.\n          if (!(error instanceof CreateosApiError && error.status === 409)) throw error;\n        }\n      } else if (!canSubmit && ![\"creating\", \"pausing\", \"resuming\"].includes(sandbox.status!)) {\n        throw new Error(`CreateOS sandbox did not reach ${desired}.`);\n      }\n      // An accepted transition can remain in its previous state briefly.\n      // Poll under the same deadline without submitting the action twice.\n      await delay(250, undefined, { signal });\n    }\n  }\n\n  async upload(id: string, path: string, content: string, signal: AbortSignal): Promise<void> {\n    const response = await this.request(`/sandboxes/${identifier(id)}/files?path=${encodeURIComponent(path)}`, {\n      method: \"PUT\", headers: { \"Content-Type\": \"application/octet-stream\" }, body: content, signal,\n    });\n    await response.body?.cancel();\n  }\n}\n","sourceCodeStart":108,"sourceCodeEnd":141,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/client.ts#L108-L141","documentation":"transition submits a lifecycle action (pause, resume, etc.) to CreateOS and then polls until the sandbox reaches the desired state within a deadline. This error is thrown when the sandbox cannot be transitioned (no action was submittable) and its current status is not one of the transient in-flight states (creating/pausing/resuming), so it will never reach the desired state on its own.","triggerScenarios":"Calling acquire/release/onEnvironmentResumeLease when the sandbox status is a terminal or incompatible state (e.g. \"failed\", \"terminated\", \"paused\" while resuming was never accepted, or a 409 was never returned because the provider rejects the action outright without a conflict).","commonSituations":"Sandbox previously failed provisioning and sits in a dead status; a CreateOS-side operation cleaned up or terminated the sandbox; an operator manually paused/destroyed the sandbox; or polling deadline logic races a state that skipped through the expected intermediate states.","solutions":["Fetch the sandbox's current status via getSandbox and inspect why it is not in a transitional state before retrying.","If the sandbox is in a terminal/failed state, create a new sandbox instead of reusing this one.","Re-run the transition after confirming no concurrent operator action is holding the sandbox; transient states resolve within the poll deadline.","Check CreateOS dashboard/API for quota, region capacity, or account issues that block state transitions."],"exampleFix":"// before: blind retry loops forever or throws\nawait provider.release(sandbox);\n// after: check status and fall back to creating a fresh sandbox\ntry {\n  await provider.release(sandbox);\n} catch (err) {\n  if (err.message.includes(\"did not reach\")) {\n    const current = await provider.getSandbox(sandbox.id);\n    if ([\"failed\", \"terminated\"].includes(current.status)) sandbox = await provider.createSandbox(signal);\n  }\n}","handlingStrategy":"retry","validationCode":"const TRANSITIONAL = [\"creating\", \"pausing\", \"resuming\"];\nconst TERMINAL = [\"failed\", \"terminated\", \"deleted\"];\nasync function canTransition(provider, sandbox, desired) {\n  const current = await provider.getSandbox(sandbox.id);\n  return !TERMINAL.includes(current.status);\n}","typeGuard":"function isTransitionable(status: string): boolean {\n  return ![\"failed\", \"terminated\", \"deleted\"].includes(status);\n}","tryCatchPattern":"try {\n  await provider.release(sandbox, signal);\n} catch (err) {\n  if (err.message.includes(\"did not reach\")) {\n    const { status } = await provider.getSandbox(sandbox.id);\n    if (isTransitionable(status)) throw err; // retry later\n    return recreateSandbox(); // terminal: replace it\n  }\n  throw err;\n}","preventionTips":["Check sandbox status before issuing pause/resume transitions.","Treat 'did not reach' as a signal to inspect current status, not to retry blindly.","Recreate sandboxes that reach terminal/failed states instead of reusing them.","Keep lease operations within the provider's poll deadline window."],"tags":["state-machine","timeout","sandbox","polling"],"backgroundTag":"invalid-state-transition","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}