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

  1. Log (outside persisted errors) what the endpoint actually returned for the failing path and compare against the expected `{status:"success", data:{...}}` envelope.
  2. Verify `config.apiUrl` points at the correct CreateOS API base URL with the `/v1` path handled by the client.
  3. Check the CreateOS provider API version; pin or upgrade the sandbox-provider plugin if the envelope schema changed.
  4. If a proxy/gateway is in front, ensure it passes the upstream JSON through unmodified rather than wrapping responses in arrays.
  5. 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

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


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)