paperclipai/paperclip · error

CreateOS connection failed.

Error message

CreateOS connection failed.

What it means

Thrown by `request()` when the underlying `fetch` to the CreateOS API rejects for a reason that is neither a caller abort nor a TimeoutError (e.g. DNS failure, connection refused/reset, TLS error). The client deliberately replaces the fetch cause with this fixed message because raw network errors can leak the configured apiUrl (and potentially credentials) into persisted errors and logs.

Solutions

  1. Verify network reachability: curl the CreateOS host from the same machine/container (`curl -v https://<api-url>/v1/...`).
  2. Check `config.apiUrl` for typos, wrong scheme (http vs https), wrong port, or trailing path mistakes.
  3. Confirm no caller-level AbortSignal was aborted before/during the request — those surface as the signal's reason instead, so seeing this message implies a genuine network-layer failure.
  4. Check egress/firewall/proxy configuration for the environment; set HTTPS_PROXY if required.
  5. Retry with backoff — transient connection resets and DNS blips are common; the client's pacer (`waitForRequest`) does not cover connection failures.
  6. Check CreateOS provider status/uptime for an outage.

Example fix

// before: assuming the error message is all you get
try { await client.getSandbox(id); } catch { /* connection failed, unknown why */ }
// after: retrying with backoff on connection failures
async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
  for (let i = 1; ; i++) {
    try { return await fn(); }
    catch (e) {
      if (i >= attempts || !(e instanceof Error) || e.message !== "CreateOS connection failed.") throw e;
      await new Promise(r => setTimeout(r, 500 * 2 ** i));
    }
  }
}
await withRetry(() => client.getSandbox(id));
Defensive patterns

Strategy: retry

Validate before calling

// Reachability check before calling the API
const url = new URL(config.apiUrl);
const ok = await fetch(`${url.protocol}//${url.host}`, { method: "HEAD", signal: AbortSignal.timeout(5000) })
  .then(() => true).catch(() => false);
if (!ok) throw new Error("CreateOS host unreachable before request.");

Try / catch

try {
  await client.getSandbox(id);
} catch (e) {
  if (e instanceof Error && e.message === "CreateOS connection failed.") {
    // network-layer failure: retry with backoff or surface as infra incident
  } else throw e;
}

Prevention

When it happens

Trigger: Any client call (getSandbox, createSandbox, destroySandbox, transition, upload, json) where the TCP/TLS connection to `config.apiUrl` cannot be established or is reset mid-flight; wrong hostname/port, offline host, firewall, dead provider endpoint. Caller aborts (init.signal aborted) and `AbortSignal.timeout` expirations are rethrown as-is instead.

Common situations: Typo or stale value in the configured apiUrl; running in an environment without egress to the CreateOS host; provider outage or IP allowlist change; corporate proxy required but not configured; DNS resolution failure in containers.

Related errors


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

Appendix: source

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

    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",
        headers: { ...init.headers, "X-Api-Key": this.apiKey },
        signal,
      });
    } catch (error) {
      if (init.signal?.aborted) throw init.signal.reason;
      // Do not propagate fetch causes: they can contain the configured URL.
      if (error instanceof Error && error.name === "TimeoutError") throw error;
      throw new Error("CreateOS connection failed.");
    }
    if (!response.ok) {
      await response.body?.cancel();
      // Only fixed operation labels: never include paths, queries, or bodies,
      // which may contain credentials or private workspace names.
      const operation = path.includes("/files?") ? "file transfer"
        : path.includes("/stdin/close") ? "stdin close"
        : path.includes("/connect?") ? "output connection"
        : path.endsWith("/processes") ? "process creation"
        : path.includes("/processes/") ? "process cleanup"
        : path.endsWith("/exec") ? "workspace command"
        : "sandbox lifecycle";
      throw new CreateosApiError(response.status, operation);
    }
    return response;
  }

  async json(path: string, method = "GET", body?: unknown, signal?: AbortSignal): Promise<Record<string, unknown>> {

View on GitHub (pinned to 3f1d897a7c)