{"record":{"id":"94aeecdcf3bc2a98","repo":"paperclipai/paperclip","slug":"createos-returned-an-unsuccessful-response","errorCode":null,"errorMessage":"CreateOS returned an unsuccessful response.","messagePattern":"CreateOS returned an unsuccessful response\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/client.ts","lineNumber":80,"sourceCode":"        : path.includes(\"/connect?\") ? \"output connection\"\n        : path.endsWith(\"/processes\") ? \"process creation\"\n        : path.includes(\"/processes/\") ? \"process cleanup\"\n        : path.endsWith(\"/exec\") ? \"workspace command\"\n        : \"sandbox lifecycle\";\n      throw new CreateosApiError(response.status, operation);\n    }\n    return response;\n  }\n\n  async json(path: string, method = \"GET\", body?: unknown, signal?: AbortSignal): Promise<Record<string, unknown>> {\n    const response = await this.request(path, {\n      method,\n      ...(body !== undefined ? { body: JSON.stringify(body), headers: { \"Content-Type\": \"application/json\" } } : {}),\n      signal,\n    });\n    let envelope: Record<string, unknown>;\n    try { envelope = object(await response.json()); } catch { throw new Error(\"CreateOS returned invalid JSON.\"); }\n    if (envelope.status !== \"success\") throw new Error(\"CreateOS returned an unsuccessful response.\");\n    return object(envelope.data);\n  }\n\n  async getSandbox(id: string, signal?: AbortSignal): Promise<Sandbox> {\n    const data = await this.json(`/sandboxes/${identifier(id)}`, \"GET\", undefined, signal);\n    if (data.id !== id || typeof data.status !== \"string\") throw new Error(\"CreateOS sandbox identity or state is invalid.\");\n    return { id, status: data.status };\n  }\n\n  async createSandbox(signal: AbortSignal): Promise<Sandbox> {\n    const { shape, rootfs, region } = this.config;\n    const data = await this.json(\"/sandboxes\", \"POST\", {\n      shape,\n      ...(rootfs ? { rootfs } : {}),\n      ...(region ? { region } : {}),\n      ingress_enabled: false,\n      // The host owns lease release. Idle pause is not a command timeout or a\n      // guaranteed expiry, and could suspend a quiet active agent.","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/client.ts#L62-L98","documentation":"Thrown by `json()` when the response parsed successfully as an object but its `status` field is not exactly the string `\"success\"`. CreateOS wraps every payload in a `{status, data}` envelope; any other `status` (e.g. `\"error\"`, `\"failed\"`, or a missing field) means the provider acknowledged the request but reported it as not successful. The error is intentionally opaque — provider failure details could leak private data, so check the provider's own dashboard/logs.","triggerScenarios":"Any `json()` call (getSandbox, createSandbox, destroySandbox, transition's resume/pause POST) where the provider responds 200 with `envelope.status` other than \"success\" — e.g. the sandbox entered an error state, an operation was rejected at the application level, or the envelope schema changed and `status` was renamed or removed.","commonSituations":"Requesting a transition on a sandbox that cannot perform it (e.g. pause on an already-paused sandbox), provider-side quota/limit rejection that still returns HTTP 200, provider API version change altering the envelope, hitting a compatible-but-different sandbox service with a different envelope convention.","solutions":["Call `getSandbox(id)` to inspect the sandbox `status` — an error/failed state usually explains the rejection.","Confirm the requested operation is legal for the sandbox's current state (only running sandboxes can pause; only paused/error can resume).","Check the CreateOS dashboard/provider logs for the sandbox to see the underlying failure reason.","Verify the provider API version hasn't changed the envelope `status` semantics or field name.","For destroySandbox, note 404s are already tolerated; other failures during cleanup can be safely retried as destroy is idempotent."],"exampleFix":"// before: treating the failure as transient\nawait client.transition(id, \"running\", signal); // may loop throwing\n// after: check sandbox state and only transition when eligible\nconst sandbox = await client.getSandbox(id);\nif (sandbox.status === \"error\") {\n  throw new Error(`Sandbox ${id} is in error state; recreate it instead of resuming.`);\n}\nawait client.transition(id, \"running\", signal);","handlingStrategy":"try-catch","validationCode":"const sandbox = await client.getSandbox(id);\nconst canResume = sandbox.status === \"paused\" || sandbox.status === \"error\";\nif (!canResume) throw new Error(`Sandbox ${id} (${sandbox.status}) cannot be resumed.`);","typeGuard":null,"tryCatchPattern":"try {\n  await client.json(`/sandboxes/${id}/resume`, \"POST\", undefined, signal);\n} catch (e) {\n  if (e instanceof Error && e.message === \"CreateOS returned an unsuccessful response.\") {\n    // check getSandbox status; recreate the sandbox if it is in error state\n  } else throw e;\n}","preventionTips":["Check sandbox status via getSandbox() before issuing transitions.","Model the provider's state machine (paused/running/error/creating/pausing/resuming) in your own code and gate operations on it.","Track provider release notes for envelope `{status,data}` changes.","Remember HTTP 200 does not imply success for this API — always rely on the envelope status, which json() already enforces."],"tags":["api","response-envelope","sandbox-provider","state"],"backgroundTag":"api-error-response","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"}