paperclipai/paperclip · error
CreateOS returned an empty process stream.
Error message
CreateOS returned an empty process stream.
What it means
events() consumes the NDJSON process stream from the CreateOS execution HTTP response. If response.body is null/undefined the plugin has no stream to read, so it throws instead of silently yielding nothing. This normally indicates the HTTP layer produced a response without a body, which the plugin never expects on a successful execution request.
Solutions
- Verify the CreateOS API URL points at the real streaming execution endpoint (correct origin and /v1 path).
- Run on a fetch implementation that supports streaming response bodies (native undici/Node 18+ fetch), not a stubbed polyfill.
- Check proxies/middleware between the plugin and CreateOS that might return an empty body for streamed endpoints.
- In tests, mock fetch with a Response containing a readable body, e.g. new Response('"{}"\n', { status: 200 }).
Example fix
// before (test mock)
global.fetch = async () => new Response(null, { status: 200 });
// after
global.fetch = async () => new Response(JSON.stringify({ type: "exit", code: 0 }) + "\n", { status: 200 }); Defensive patterns
Strategy: try-catch
Type guard
function hasBody(res) { return res != null && res.body != null; } Try / catch
try {
for await (const ev of plugin.execute(params)) { /* ... */ }
} catch (e) {
if (e.message.includes('empty process stream')) {
throw new Error('Execution response had no body; check the API URL/endpoint and your fetch runtime: ' + e.message);
}
throw e;
} Prevention
- Use a runtime with native streaming fetch (Node 18+ undici)
- Mock fetch in tests with Response objects that carry a real body
- Verify proxies preserve streaming response bodies for the execution endpoint
When it happens
Trigger: Calling execute when the fetch Response for the process stream has body === null — e.g. the runtime returned an opaque/buffered response, a service-worker interception stripped the body, or the client was constructed in an environment without streaming body support.
Common situations: Running the plugin in a non-Node fetch polyfill that doesn't expose response.body; a proxy returning 204/empty responses for the stream endpoint; fetch mocked in tests without a body.
Related errors
- CreateOS file download has no body.
- CreateOS process stream reported an error.
- CreateOS returned an unknown process event.
- A sandbox command is required.
- [adapter-ui-loader] Failed to load UI parser for
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/08092dfc21f6979a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/execute.ts:33
}
function commandScript(params: PluginEnvironmentExecuteParams, stdinPath: string | null): string {
if (!params.command) throw new Error("A sandbox command is required.");
const env = Object.entries(params.env ?? {}).map(([key, value]) => {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof value !== "string") {
throw new Error("Invalid sandbox environment variable.");
}
return `${key}=${shellQuote(value)}`;
});
const command = [params.command, ...(params.args ?? [])].map(shellQuote).join(" ");
return [
params.cwd ? `cd -- ${shellQuote(params.cwd)} || exit` : "",
`exec env ${env.join(" ")} ${command}${stdinPath ? ` < ${shellQuote(stdinPath)}` : ""}`,
].filter(Boolean).join("\n");
}
async function* events(response: Response): AsyncGenerator<Record<string, unknown>> {
if (!response.body) throw new Error("CreateOS returned an empty process stream.");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let pending = "";
try {
for (;;) {
const { value, done } = await reader.read();
pending += done ? decoder.decode() : decoder.decode(value, { stream: true });
let newline: number;
while ((newline = pending.indexOf("\n")) >= 0) {
const line = pending.slice(0, newline);
pending = pending.slice(newline + 1);
if (line.length > MAX_LINE_BYTES) throw new Error("CreateOS process frame is too large.");
if (line.trim()) yield parseEvent(line);
}
if (pending.length > MAX_LINE_BYTES) throw new Error("CreateOS process frame is too large.");
if (done) {
if (pending.trim()) yield parseEvent(pending);
return;View on GitHub (pinned to 3f1d897a7c)