{"record":{"id":"b395ec6d9615847f","repo":"paperclipai/paperclip","slug":"createos-does-not-yet-support-leases-with-a-guaranteed","errorCode":null,"errorMessage":"CreateOS does not yet support leases with a guaranteed expiration deadline.","messagePattern":"CreateOS does not yet support leases with a guaranteed expiration deadline\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/plugin.ts","lineNumber":24,"sourceCode":"} from \"@paperclipai/plugin-sdk\";\nimport { CreateosApiError, CreateosClient, object } from \"./client.js\";\nimport { parseConfig, resolveApiKey } from \"./config.js\";\nimport { CreateosCleanupError, execute, shellQuote } from \"./execute.js\";\nimport { syncFiles } from \"./file-sync.js\";\n\nconst CWD = \"/paperclip-workspace\";\n// The host excludes .paperclip-runtime from workspace export, so the lease\n// marker never becomes a user repository file.\nconst MARKER = `${CWD}/.paperclip-runtime/.paperclip-createos-lease`;\n\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,","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/plugin.ts#L6-L42","documentation":"acquire() creates a CreateOS sandbox lease, but the provider cannot guarantee a hard expiration at a caller-requested time: sandboxes only enforce an idle timeout, and the plugin refuses to promise an expiry it cannot enforce. If the caller supplies requestedExpiresAt, acquire throws immediately instead of creating a lease that might outlive the deadline.","triggerScenarios":"onEnvironmentProbe → acquire is called with params.requestedExpiresAt set (non-null), e.g. the orchestrator asks for a lease guaranteed to end at a specific timestamp.","commonSituations":"Budget or governance policies that require time-boxed leases; callers porting from providers that support deadline leases; scheduling code that always sets an expiry by default.","solutions":["Call acquire without requestedExpiresAt and rely on the sandbox idle timeout / explicit lease release.","Schedule your own release (plugin.release or delete sandbox) with a host-local timer if a soft deadline suffices.","If a guaranteed expiry is a hard requirement, use a different sandbox provider that supports provider-side expiration deadlines."],"exampleFix":"// before\nawait acquire({ companyId, environmentId, config, requestedExpiresAt: new Date(Date.now() + 3600_000) });\n// after\nawait acquire({ companyId, environmentId, config }); // manage release yourself","handlingStrategy":"validation","validationCode":"if (params.requestedExpiresAt != null) {\n  throw new Error(\"createos leases cannot guarantee expiration; omit requestedExpiresAt\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  lease = await acquire(params);\n} catch (err) {\n  if (err.message.includes(\"guaranteed expiration\")) {\n    lease = await acquire({ ...params, requestedExpiresAt: undefined });\n  } else throw err;\n}","preventionTips":["Do not set requestedExpiresAt for CreateOS leases","Implement your own release timer if a soft deadline is needed","Choose a deadline-capable provider when hard expiry is mandatory"],"tags":["sandbox","lease","unsupported-feature","expiration"],"backgroundTag":"unsupported-operation","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}