{"record":{"id":"e2b3321e75ce7f4c","repo":"paperclipai/paperclip","slug":"createos-workspace-preparation-failed-the-image-must-provide","errorCode":null,"errorMessage":"CreateOS workspace preparation failed; the image must provide Bash.","messagePattern":"CreateOS workspace preparation failed; the image must provide Bash\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/plugin.ts","lineNumber":34,"sourceCode":"\nfunction metadataMatches(params: PluginEnvironmentDriverBaseParams, metadata?: Record<string, unknown>): boolean {\n  return metadata?.provider === \"createos\" && metadata.companyId === params.companyId &&\n    metadata.environmentId === params.environmentId && metadata.apiUrl === parseConfig(params.config).apiUrl;\n}\n\nasync function acquire(params: PluginEnvironmentAcquireLeaseParams): Promise<PluginEnvironmentLease> {\n  // An idle timeout or a host-local timer cannot supply a provider expiry.\n  if (params.requestedExpiresAt) throw new Error(\"CreateOS does not yet support leases with a guaranteed expiration deadline.\");\n  const config = parseConfig(params.config);\n  const client = new CreateosClient(config);\n  const signal = AbortSignal.timeout(config.timeoutMs);\n  const sandbox = await client.createSandbox(signal);\n  try {\n    await client.transition(sandbox.id, \"running\", signal);\n    const data = await client.json(`/sandboxes/${sandbox.id}/exec`, \"POST\", {\n      cmd: \"/bin/bash\", args: [\"-lc\", `mkdir -p -- ${shellQuote(CWD)}`],\n    }, signal);\n    if (object(data.result).exit_code !== 0) throw new Error(\"CreateOS workspace preparation failed; the image must provide Bash.\");\n    const marker = randomUUID();\n    await client.upload(sandbox.id, MARKER, marker, signal);\n    return {\n      providerLeaseId: sandbox.id,\n      metadata: {\n        provider: \"createos\", apiUrl: config.apiUrl,\n        companyId: params.companyId, environmentId: params.environmentId,\n        remoteCwd: CWD, shellCommand: \"bash\", marker,\n        shape: config.shape, rootfs: config.rootfs, region: config.region,\n        reuseLease: config.reuseLease,\n      },\n    };\n  } catch (error) {\n    try { await client.destroySandbox(sandbox.id); }\n    catch { throw new Error(`CreateOS setup failed and cleanup is unconfirmed for sandbox ${sandbox.id}.`); }\n    throw error;\n  }\n}","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/plugin.ts#L16-L52","documentation":"During acquire(), the plugin prepares the sandbox workspace by running `/bin/bash -lc 'mkdir -p <CWD>'` via the sandbox exec endpoint. If the sandbox reports a non-zero exit_code, the image is missing a working Bash (or failed to create the workspace directory), so the plugin aborts lease acquisition with this error.","triggerScenarios":"POST /sandboxes/{id}/exec with cmd /bin/bash returns result.exit_code !== 0 — the image lacks /bin/bash, bash fails to start (missing libs), or mkdir -p fails due to permissions or a read-only root filesystem.","commonSituations":"Using a minimal distro image (alpine without bash, distroless, scratch); a broken image build where bash is removed; sandbox root filesystem mounted read-only; image misconfiguration after a base-image upgrade.","solutions":["Build the sandbox image with bash installed (e.g. `apk add bash` on alpine or use a bash-including base image).","Verify inside the image that `/bin/bash` exists and runs: docker run --rm <image> /bin/bash -lc 'echo ok'.","Check the workspace directory path (CWD) is creatable and the root filesystem is writable.","Inspect the exec endpoint's full result/stderr for the underlying failure if bash exists but exits non-zero."],"exampleFix":"// Dockerfile before (alpine, no bash)\nFROM alpine:3.19\n// after\nFROM alpine:3.19\nRUN apk add --no-cache bash","handlingStrategy":"validation","validationCode":"// verify the image before acquisition\nawait execInImage(image, \"/bin/bash\", [\"-lc\", \"echo ok\"]); // must exit 0","typeGuard":null,"tryCatchPattern":"try {\n  lease = await acquire(params);\n} catch (err) {\n  if (err.message.includes(\"must provide Bash\")) {\n    // rebuild/select an image with bash installed, then retry\n  } else throw err;\n}","preventionTips":["Install bash in every sandbox image (apk add bash / apt-get install bash)","Smoke-test images with /bin/bash -lc 'echo ok' in CI","Avoid distroless/scratch bases for sandbox images","Keep root filesystem writable for workspace creation"],"tags":["sandbox","image","bash","environment-setup"],"backgroundTag":"command-not-found","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}