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
- Verify the remote path exists and is a regular file inside the sandbox before syncing (run `test -f <path>` via the exec endpoint).
- Check that apiUrl points at the correct CreateOS API and that no proxy strips response bodies.
- 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
- Always verify remote file existence before download
- Keep CreateosClient and provider API versions in sync
- Avoid proxies that can strip response bodies
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
- CreateOS returned an empty process stream.
- CreateOS returned an unknown process event.
- A sandbox command is required.
- [adapter-ui-loader] Failed to load UI parser for
- Announcement request failed
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)