paperclipai/paperclip · error

CreateOS file download has no body.

Error message

CreateOS file download has no body.

What it means

When downloading a file from the CreateOS sandbox via `GET /sandboxes/{id}/files`, the plugin requires the HTTP response to carry a streaming body. If `response.body` is null/undefined, there is nothing to pipe into the local file, so this error is thrown. It typically indicates the provider returned an unexpected response (e.g. an error response without content) or the client implementation stripped the body.

Solutions

  1. Verify the remote path exists and is a regular file inside the sandbox before syncing (run `test -f <path>` via the exec endpoint).
  2. Check that apiUrl points at the correct CreateOS API and that no proxy strips response bodies.
  3. Confirm the CreateosClient/API surface version matches the provider API; update the plugin or client if the endpoint contract changed.

Example fix

// before: blind download
await download("/work/out.json", "/local/out.json");
// after: verify it is a file first
await run(`test -f /work/out.json`);
await download("/work/out.json", "/local/out.json");
Defensive patterns

Strategy: try-catch

Validate before calling

// verify the remote path is a regular file before downloading
await run(`test -f ${shellQuote(remote)}`);

Try / catch

try {
  await download(remote, local);
} catch (err) {
  if (err.message.includes("no body")) {
    // treat as missing/invalid remote path; check via exec before retrying
  } else throw err;
}

Prevention

When it happens

Trigger: download() is called for a remote path and client.request() resolves with a response whose `body` is null — e.g. the provider returned a 204/error-shaped response, the path is a directory or missing but the API did not surface an error status, or a custom/mock client does not populate body.

Common situations: Requesting a remote path that does not exist or is a directory; provider API version change altering the files endpoint response; misconfigured apiUrl pointing at a proxy that drops bodies; testing with a stubbed CreateosClient.

Related errors


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

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/file-sync.ts:86

    assertRemotePath(cwd);
    const result = await execute(client, { ...params, command: "/bin/bash", args: ["-c", command], cwd },
      timeoutMs == null ? signal : AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]));
    if (result.timedOut || result.exitCode !== 0) throw new Error("CreateOS transfer command failed.");
  };
  const upload = async (local: string, remote: string) => {
    const source = createReadStream(local);
    try {
      const init: RequestInit & { duplex: "half" } = {
        method: "PUT", body: Readable.toWeb(source) as ReadableStream<Uint8Array>,
        duplex: "half", headers: { "Content-Type": "application/octet-stream" }, signal,
      };
      const response = await client.request(`/sandboxes/${id}/files?path=${encodeURIComponent(remote)}`, init);
      await response.body?.cancel();
    } finally { source.destroy(); }
  };
  const download = async (remote: string, local: string, mode = 0o600) => {
    const response = await client.request(`/sandboxes/${id}/files?path=${encodeURIComponent(remote)}`, { signal });
    if (!response.body) throw new Error("CreateOS file download has no body.");
    await pipeline(response.body, createWriteStream(local, { flags: "wx", mode }), { signal });
  };

  // Validate every mapping before beginning side effects. Host paths are
  // orchestrator-authored and checked by its source/target-root guard.
  for (const operation of params.operations) {
    for (const mapping of operation.files) {
      if (!["file", "directory"].includes(mapping.kind)) throw new Error("Unsupported CreateOS transfer kind.");
      if (!path.isAbsolute(direction === "in" ? mapping.sourcePath : mapping.targetPath)) throw new Error("CreateOS transfer requires an absolute host path.");
      if (mapping.mode != null && (!Number.isInteger(mapping.mode) || mapping.mode < 0 || mapping.mode > 0o777)) throw new Error("Invalid CreateOS file mode.");
      assertRemotePath(direction === "in" ? mapping.targetPath : mapping.sourcePath);
    }
    for (const command of operation.postUploadCommands ?? []) {
      assertRemotePath(command.cwd ?? ROOT);
      if (command.timeoutMs != null && (!Number.isInteger(command.timeoutMs) || command.timeoutMs < 1 || command.timeoutMs > 86_400_000)) throw new Error("Invalid CreateOS transfer timeout.");
    }
    if (direction === "out" && operation.postUploadCommands?.length) throw new Error("Outbound CreateOS transfers cannot run post-upload commands.");
  }

View on GitHub (pinned to 3f1d897a7c)