paperclipai/paperclip · error

CreateOS sandbox did not reach

Error message

CreateOS sandbox did not reach ${desired}.

What it means

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.

Solutions

  1. Fetch the sandbox's current status via getSandbox and inspect why it is not in a transitional state before retrying.
  2. If the sandbox is in a terminal/failed state, create a new sandbox instead of reusing this one.
  3. Re-run the transition after confirming no concurrent operator action is holding the sandbox; transient states resolve within the poll deadline.
  4. Check CreateOS dashboard/API for quota, region capacity, or account issues that block state transitions.

Example fix

// before: blind retry loops forever or throws
await provider.release(sandbox);
// after: check status and fall back to creating a fresh sandbox
try {
  await provider.release(sandbox);
} catch (err) {
  if (err.message.includes("did not reach")) {
    const current = await provider.getSandbox(sandbox.id);
    if (["failed", "terminated"].includes(current.status)) sandbox = await provider.createSandbox(signal);
  }
}
Defensive patterns

Strategy: retry

Validate before calling

const TRANSITIONAL = ["creating", "pausing", "resuming"];
const TERMINAL = ["failed", "terminated", "deleted"];
async function canTransition(provider, sandbox, desired) {
  const current = await provider.getSandbox(sandbox.id);
  return !TERMINAL.includes(current.status);
}

Type guard

function isTransitionable(status: string): boolean {
  return !["failed", "terminated", "deleted"].includes(status);
}

Try / catch

try {
  await provider.release(sandbox, signal);
} catch (err) {
  if (err.message.includes("did not reach")) {
    const { status } = await provider.getSandbox(sandbox.id);
    if (isTransitionable(status)) throw err; // retry later
    return recreateSandbox(); // terminal: replace it
  }
  throw err;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/2deca77ef3af053d. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/client.ts:126

  async transition(id: string, desired: "running" | "paused", signal: AbortSignal): Promise<void> {
    let submitted = false;
    for (;;) {
      signal.throwIfAborted();
      const sandbox = await this.getSandbox(id, signal);
      if (sandbox.status === desired) return;
      const canSubmit = desired === "running"
        ? ["paused", "error"].includes(sandbox.status!)
        : sandbox.status === "running";
      if (canSubmit && !submitted) {
        try {
          await this.json(`/sandboxes/${id}/${desired === "running" ? "resume" : "pause"}`, "POST", undefined, signal);
          submitted = true;
        } catch (error) {
          // A concurrent state transition is reconciled by reading its state.
          if (!(error instanceof CreateosApiError && error.status === 409)) throw error;
        }
      } else if (!canSubmit && !["creating", "pausing", "resuming"].includes(sandbox.status!)) {
        throw new Error(`CreateOS sandbox did not reach ${desired}.`);
      }
      // An accepted transition can remain in its previous state briefly.
      // Poll under the same deadline without submitting the action twice.
      await delay(250, undefined, { signal });
    }
  }

  async upload(id: string, path: string, content: string, signal: AbortSignal): Promise<void> {
    const response = await this.request(`/sandboxes/${identifier(id)}/files?path=${encodeURIComponent(path)}`, {
      method: "PUT", headers: { "Content-Type": "application/octet-stream" }, body: content, signal,
    });
    await response.body?.cancel();
  }
}

View on GitHub (pinned to 3f1d897a7c)