paperclipai/paperclip · error

CreateOS sandbox identity or state is invalid.

Error message

CreateOS sandbox identity or state is invalid.

What it means

getSandbox fetches a sandbox from the CreateOS API and validates that the returned record matches the requested ID and carries a string status. The library throws this error when the response body fails either check, meaning the provider returned a sandbox record that cannot be trusted as the identity/state of the requested sandbox. It guards callers against acting on stale or mismatched API data.

Solutions

  1. Log the returned data object and compare data.id to the requested id to find the mismatch source.
  2. Verify apiUrl points at the correct CreateOS environment/tenant that actually owns this sandbox id.
  3. Check the CreateOS API version; upgrade or pin the plugin if the response schema (id/status fields) changed.
  4. Disable or audit any proxy/CDN between the plugin and CreateOS that could alter or substitute the response.

Example fix

// before: assuming data matches
const data = await this.json(`/sandboxes/${identifier(id)}`, "GET", undefined, signal);
if (data.id !== id || typeof data.status !== "string") throw new Error("CreateOS sandbox identity or state is invalid.");
// after: tolerate a wrapped/alternate payload before throwing
const data = await this.json(`/sandboxes/${identifier(id)}`, "GET", undefined, signal);
const record = data.sandbox ?? data;
if (record.id !== id || typeof record.status !== "string") throw new Error(`CreateOS returned sandbox ${record.id} (status: ${String(record.status)}) for requested id ${id}.`);
Defensive patterns

Strategy: validation

Validate before calling

function assertSandboxResponse(data, requestedId) {
  if (!data || typeof data !== "object") throw new Error("CreateOS response is not an object");
  if (typeof data.id !== "string" || data.id !== requestedId) throw new Error(`CreateOS returned id ${data.id}, expected ${requestedId}`);
  if (typeof data.status !== "string") throw new Error(`CreateOS returned non-string status: ${typeof data.status}`);
  return true;
}

Type guard

function isSandbox(v: unknown): v is { id: string; status: string } {
  return typeof v === "object" && v !== null &&
    typeof (v as any).id === "string" && typeof (v as any).status === "string";
}

Try / catch

try {
  const sandbox = await provider.getSandbox(id);
} catch (err) {
  if (err.message.includes("identity or state is invalid")) {
    logger.warn({ id }, "CreateOS sandbox response failed identity check; refetching or recreating");
    sandbox = await recreateSandbox();
  } else throw err;
}

Prevention

When it happens

Trigger: Calling getSandbox(id) when the CreateOS API returns a successful envelope whose data.id differs from the requested id, or whose data.status is missing or not a string (e.g. null status, snake_case key like sandbox_status, or an error-shaped object wrapped in a 200 response).

Common situations: Hitting a different CreateOS environment than configured (data belongs to another tenant so IDs never match), a proxy/gateway rewriting the response, a CreateOS API version change renaming the status field, or caching layers returning a different sandbox record.

Related errors


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

Appendix: source

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

    }
    return response;
  }

  async json(path: string, method = "GET", body?: unknown, signal?: AbortSignal): Promise<Record<string, unknown>> {
    const response = await this.request(path, {
      method,
      ...(body !== undefined ? { body: JSON.stringify(body), headers: { "Content-Type": "application/json" } } : {}),
      signal,
    });
    let envelope: Record<string, unknown>;
    try { envelope = object(await response.json()); } catch { throw new Error("CreateOS returned invalid JSON."); }
    if (envelope.status !== "success") throw new Error("CreateOS returned an unsuccessful response.");
    return object(envelope.data);
  }

  async getSandbox(id: string, signal?: AbortSignal): Promise<Sandbox> {
    const data = await this.json(`/sandboxes/${identifier(id)}`, "GET", undefined, signal);
    if (data.id !== id || typeof data.status !== "string") throw new Error("CreateOS sandbox identity or state is invalid.");
    return { id, status: data.status };
  }

  async createSandbox(signal: AbortSignal): Promise<Sandbox> {
    const { shape, rootfs, region } = this.config;
    const data = await this.json("/sandboxes", "POST", {
      shape,
      ...(rootfs ? { rootfs } : {}),
      ...(region ? { region } : {}),
      ingress_enabled: false,
      // The host owns lease release. Idle pause is not a command timeout or a
      // guaranteed expiry, and could suspend a quiet active agent.
    }, signal);
    return { id: identifier(data.id) };
  }

  async destroySandbox(id: string): Promise<void> {
    try { await this.json(`/sandboxes/${identifier(id)}`, "DELETE"); }

View on GitHub (pinned to 3f1d897a7c)