{"record":{"id":"a8b55ba9a39af80d","repo":"paperclipai/paperclip","slug":"createos-returned-an-invalid-response","errorCode":null,"errorMessage":"CreateOS returned an invalid response.","messagePattern":"CreateOS returned an invalid response\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/client.ts","lineNumber":21,"sourceCode":"import { resolveApiKey } from \"./config.js\";\nimport { waitForRequest } from \"./request-pacer.js\";\n\nexport class CreateosApiError extends Error {\n  constructor(readonly status: number, operation?: string) {\n    // Provider bodies may echo command input or credentials. Keep them out of\n    // persisted errors and probe metadata.\n    super(`CreateOS request failed (HTTP ${status})${operation ? ` during ${operation}` : \"\"}.`);\n  }\n}\n\nexport interface Sandbox {\n  id: string;\n  status?: string;\n}\n\nexport function object(value: unknown): Record<string, unknown> {\n  if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n    throw new Error(\"CreateOS returned an invalid response.\");\n  }\n  return value as Record<string, unknown>;\n}\n\nexport function identifier(value: unknown): string {\n  if (typeof value !== \"string\" || !/^[A-Za-z0-9_-]{1,200}$/.test(value)) {\n    throw new Error(\"CreateOS returned an invalid resource ID.\");\n  }\n  return value;\n}\n\nexport class CreateosClient {\n  readonly apiKey: string;\n  constructor(readonly config: CreateosConfig) {\n    this.apiKey = resolveApiKey(config);\n  }\n\n  async request(path: string, init: RequestInit = {}): Promise<Response> {","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/client.ts#L3-L39","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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()`."],"exampleFix":"// before: trusting the response shape\nconst data = await this.json(\"/sandboxes\", \"POST\", payload, signal);\nreturn { id: identifier(data.id) };\n// after: validating before use\nconst data = await this.json(\"/sandboxes\", \"POST\", payload, signal);\nif (!data || typeof data !== \"object\" || Array.isArray(data) || typeof data.id !== \"string\") {\n  throw new Error(\"CreateOS createSandbox returned no sandbox object.\");\n}\nreturn { id: identifier(data.id) };","handlingStrategy":"type-guard","validationCode":"const body = await response.json();\nif (body === null || typeof body !== \"object\" || Array.isArray(body)) {\n  throw new Error(\"Expected a JSON object envelope from CreateOS.\");\n}","typeGuard":"function isRecord(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const envelope = await client.json(path);\n  // use envelope\n} catch (e) {\n  if (e instanceof Error && e.message === \"CreateOS returned an invalid response.\") {\n    // fall back: re-fetch or inspect raw body out-of-band for diagnosis\n  } else throw e;\n}","preventionTips":["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."],"tags":["api","response-validation","json","sandbox-provider"],"backgroundTag":"unexpected-response-shape","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}