paperclipai/paperclip · error
CreateOS returned an invalid response.
Error message
CreateOS returned an invalid response.
What it means
`object()` is the CreateOS client's response-shape validator: it asserts the provider returned a plain JSON object (not null, a primitive, or an array). The error means the CreateOS API returned a body whose top level is not an object, so the client cannot extract the `{status, data}` envelope it expects. It is thrown defensively whenever any response payload is coerced to a record.
Solutions
- Log (outside persisted errors) what the endpoint actually returned for the failing path and compare against the expected `{status:"success", data:{...}}` envelope.
- Verify `config.apiUrl` points at the correct CreateOS API base URL with the `/v1` path handled by the client.
- Check the CreateOS provider API version; pin or upgrade the sandbox-provider plugin if the envelope schema changed.
- If a proxy/gateway is in front, ensure it passes the upstream JSON through unmodified rather than wrapping responses in arrays.
- If envelope.data is an array for a legitimately list-shaped result, adjust the caller to use the correct endpoint rather than loosening `object()`.
Example fix
// before: trusting the response shape
const data = await this.json("/sandboxes", "POST", payload, signal);
return { id: identifier(data.id) };
// after: validating before use
const data = await this.json("/sandboxes", "POST", payload, signal);
if (!data || typeof data !== "object" || Array.isArray(data) || typeof data.id !== "string") {
throw new Error("CreateOS createSandbox returned no sandbox object.");
}
return { id: identifier(data.id) }; Defensive patterns
Strategy: type-guard
Validate before calling
const body = await response.json();
if (body === null || typeof body !== "object" || Array.isArray(body)) {
throw new Error("Expected a JSON object envelope from CreateOS.");
} Type guard
function isRecord(v: unknown): v is Record<string, unknown> {
return typeof v === "object" && v !== null && !Array.isArray(v);
} Try / catch
try {
const envelope = await client.json(path);
// use envelope
} catch (e) {
if (e instanceof Error && e.message === "CreateOS returned an invalid response.") {
// fall back: re-fetch or inspect raw body out-of-band for diagnosis
} else throw e;
} Prevention
- Pin and monitor the CreateOS provider API version; envelope changes break shape assumptions.
- Keep a smoke test that asserts the `{status,data}` envelope for each endpoint used.
- Never point apiUrl at gateways that wrap or transform responses.
- Validate responses at the boundary with isRecord() before accessing nested fields.
When it happens
Trigger: Calling `object()` with a non-object value: `response.json()` resolving to null, a number/string/boolean, or a top-level JSON array; also called on `envelope.data` in `json()` (client.ts:81), so a success envelope whose `data` is an array or primitive triggers it. In practice: a proxy or gateway returning an array of errors, a misconfigured apiUrl hitting a different service, or a provider API version change reshaping responses.
Common situations: The configured `apiUrl` points at a wrong endpoint that returns JSON arrays or scalars; an API gateway (nginx/CloudFront) or capture layer returns `[]` or `null`; the provider ships a breaking API change moving the envelope; a mock/test server returns a bare list.
Related errors
- CreateOS returned invalid JSON.
- CreateOS returned an invalid resource ID.
- CreateOS returned an unsuccessful response.
- Chat SDK state is not JSON-serializable
- "configJson" is required and must be an object
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/a8b55ba9a39af80d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/client.ts:21
import { resolveApiKey } from "./config.js";
import { waitForRequest } from "./request-pacer.js";
export class CreateosApiError extends Error {
constructor(readonly status: number, operation?: string) {
// Provider bodies may echo command input or credentials. Keep them out of
// persisted errors and probe metadata.
super(`CreateOS request failed (HTTP ${status})${operation ? ` during ${operation}` : ""}.`);
}
}
export interface Sandbox {
id: string;
status?: string;
}
export function object(value: unknown): Record<string, unknown> {
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("CreateOS returned an invalid response.");
}
return value as Record<string, unknown>;
}
export function identifier(value: unknown): string {
if (typeof value !== "string" || !/^[A-Za-z0-9_-]{1,200}$/.test(value)) {
throw new Error("CreateOS returned an invalid resource ID.");
}
return value;
}
export class CreateosClient {
readonly apiKey: string;
constructor(readonly config: CreateosConfig) {
this.apiKey = resolveApiKey(config);
}
async request(path: string, init: RequestInit = {}): Promise<Response> {View on GitHub (pinned to 3f1d897a7c)