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
- Log the returned data object and compare data.id to the requested id to find the mismatch source.
- Verify apiUrl points at the correct CreateOS environment/tenant that actually owns this sandbox id.
- Check the CreateOS API version; upgrade or pin the plugin if the response schema (id/status fields) changed.
- 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
- Pin the CreateOS API version and verify response schema after provider upgrades.
- Log the raw response body on mismatch to diagnose tenant/environment drift.
- Avoid proxies that rewrite API responses between the plugin and CreateOS.
- Always cross-check data.id against the requested id before caching sandbox records.
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
- CreateOS returned an invalid resource ID.
- invalid_provider
- A full lowercase source SHA is required.
- A reusable lease cannot be replaced and reacquired in the…
- A reusable lease handoff requires an execution workspace…
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)