{"record":{"id":"08092dfc21f6979a","repo":"paperclipai/paperclip","slug":"createos-returned-an-empty-process-stream","errorCode":null,"errorMessage":"CreateOS returned an empty process stream.","messagePattern":"CreateOS returned an empty process stream\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/execute.ts","lineNumber":33,"sourceCode":"}\n\nfunction commandScript(params: PluginEnvironmentExecuteParams, stdinPath: string | null): string {\n  if (!params.command) throw new Error(\"A sandbox command is required.\");\n  const env = Object.entries(params.env ?? {}).map(([key, value]) => {\n    if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || typeof value !== \"string\") {\n      throw new Error(\"Invalid sandbox environment variable.\");\n    }\n    return `${key}=${shellQuote(value)}`;\n  });\n  const command = [params.command, ...(params.args ?? [])].map(shellQuote).join(\" \");\n  return [\n    params.cwd ? `cd -- ${shellQuote(params.cwd)} || exit` : \"\",\n    `exec env ${env.join(\" \")} ${command}${stdinPath ? ` < ${shellQuote(stdinPath)}` : \"\"}`,\n  ].filter(Boolean).join(\"\\n\");\n}\n\nasync function* events(response: Response): AsyncGenerator<Record<string, unknown>> {\n  if (!response.body) throw new Error(\"CreateOS returned an empty process stream.\");\n  const reader = response.body.getReader();\n  const decoder = new TextDecoder();\n  let pending = \"\";\n  try {\n    for (;;) {\n      const { value, done } = await reader.read();\n      pending += done ? decoder.decode() : decoder.decode(value, { stream: true });\n      let newline: number;\n      while ((newline = pending.indexOf(\"\\n\")) >= 0) {\n        const line = pending.slice(0, newline);\n        pending = pending.slice(newline + 1);\n        if (line.length > MAX_LINE_BYTES) throw new Error(\"CreateOS process frame is too large.\");\n        if (line.trim()) yield parseEvent(line);\n      }\n      if (pending.length > MAX_LINE_BYTES) throw new Error(\"CreateOS process frame is too large.\");\n      if (done) {\n        if (pending.trim()) yield parseEvent(pending);\n        return;","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/execute.ts#L15-L51","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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 })."],"exampleFix":"// before (test mock)\nglobal.fetch = async () => new Response(null, { status: 200 });\n// after\nglobal.fetch = async () => new Response(JSON.stringify({ type: \"exit\", code: 0 }) + \"\\n\", { status: 200 });","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function hasBody(res) { return res != null && res.body != null; }","tryCatchPattern":"try {\n  for await (const ev of plugin.execute(params)) { /* ... */ }\n} catch (e) {\n  if (e.message.includes('empty process stream')) {\n    throw new Error('Execution response had no body; check the API URL/endpoint and your fetch runtime: ' + e.message);\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["stream","http","empty-response","sandbox"],"backgroundTag":"empty-response-body","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}