paperclipai/paperclip · error

CreateOS returned an invalid resource ID.

Error message

CreateOS returned an invalid resource ID.

What it means

`identifier()` validates resource IDs returned by (or passed to) the CreateOS API against `^[A-Za-z0-9_-]{1,200}$`. This error means the provider returned an id that is missing, not a string, or contains characters outside the allowed set (or exceeds 200 chars). The client throws it rather than passing a malformed id into URLs where it could enable injection or break routing.

Solutions

  1. Inspect the create/get response `data` object and confirm the `id` field exists and is a string.
  2. Ensure the id value came from a CreateOS response, not from user input or a differently-shaped upstream record.
  3. Check for provider API version changes that renamed or restructured the id field.
  4. Regenerate/recreate the sandbox; an empty or malformed id often indicates the create actually failed upstream.
  5. If your own stored ids may be malformed, validate them with the same regex before calling client methods.

Example fix

// before: passing through an unvetted id
await client.destroySandbox(someId);
// after: validating first
if (typeof someId !== "string" || !/^[A-Za-z0-9_-]{1,200}$/.test(someId)) {
  throw new Error("Refusing to use malformed sandbox id.");
}
await client.destroySandbox(someId);
Defensive patterns

Strategy: validation

Validate before calling

const ID_RE = /^[A-Za-z0-9_-]{1,200}$/;
function assertValidId(id: unknown): asserts id is string {
  if (typeof id !== "string" || !ID_RE.test(id)) throw new Error("Invalid sandbox id.");
}
assertValidId(id);
await client.getSandbox(id);

Type guard

function isSandboxId(v: unknown): v is string {
  return typeof v === "string" && /^[A-Za-z0-9_-]{1,200}$/.test(v);
}

Try / catch

try {
  await client.destroySandbox(id);
} catch (e) {
  if (e instanceof Error && e.message === "CreateOS returned an invalid resource ID.") {
    // id came from a bad source; treat the record as corrupt and skip/recreate
  } else throw e;
}

Prevention

When it happens

Trigger: `createSandbox()` calling `identifier(data.id)` when the create response omits `id` or returns a non-string; `getSandbox()`/`destroySandbox()`/`upload()`/`transition()` calling `identifier(id)` with an id previously read from a bad source; a provider response echoing an empty string or an id containing slashes, dots, or whitespace.

Common situations: Provider API change renaming the id field (e.g. `sandboxId`); a stub/mock server returning placeholder ids; a sandbox id captured from logs or user input containing path characters; truncation of long ids by an intermediary.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/06ed4ff1939bc3fe. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/client.ts:28

    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> {
    const signal = init.signal ?? AbortSignal.timeout(this.config.timeoutMs);
    await waitForRequest(this.config.apiUrl, signal);
    let response: Response;
    try {
      response = await fetch(`${this.config.apiUrl}/v1${path}`, {
        ...init,
        redirect: "error",

View on GitHub (pinned to 3f1d897a7c)