{"record":{"id":"6aa90ab4aee42b02","repo":"paperclipai/paperclip","slug":"createos-process-cleanup-is-unconfirmed-destroy-this-lease","errorCode":null,"errorMessage":"CreateOS process cleanup is unconfirmed; destroy this lease before reusing it.","messagePattern":"CreateOS process cleanup is unconfirmed; destroy this lease before reusing it\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/plugin.ts","lineNumber":119,"sourceCode":"    }\n  }\n\n  async function release(params: PluginEnvironmentReleaseLeaseParams, destroy: boolean) {\n    const id = params.providerLeaseId;\n    if (!id) return;\n    if (!metadataMatches(params, params.leaseMetadata)) throw new Error(\"CreateOS lease does not belong to this environment.\");\n    const scope = key(params, id);\n    if (closing.has(scope)) throw new Error(\"CreateOS lease cleanup is already in progress.\");\n    closing.add(scope);\n    try {\n      await stopActive(scope);\n      const config = parseConfig(params.config);\n      const client = new CreateosClient(config);\n      if (destroy || !config.reuseLease) {\n        await client.destroySandbox(id);\n        unconfirmedCleanup.delete(scope);\n      } else {\n        if (unconfirmedCleanup.has(scope)) throw new Error(\"CreateOS process cleanup is unconfirmed; destroy this lease before reusing it.\");\n        try { await client.transition(id, \"paused\", AbortSignal.timeout(config.timeoutMs)); }\n        catch (error) { if (!(error instanceof CreateosApiError && error.status === 404)) throw error; }\n      }\n    } finally {\n      closing.delete(scope);\n    }\n  }\n\n  return definePlugin({\n    async setup(context) { ctx = context; ctx.logger.info(\"CreateOS sandbox provider ready\"); },\n    async onHealth() { return { status: \"ok\", message: \"CreateOS provider loaded; probe an environment to check connectivity.\" }; },\n    async onEnvironmentValidateConfig(params) {\n      try { return { ok: true, normalizedConfig: { ...parseConfig(params.config) } }; }\n      catch (error) { return { ok: false, errors: [error instanceof Error ? error.message : \"Invalid CreateOS configuration.\"] }; }\n    },\n    async onEnvironmentProbe(params) {\n      let lease: PluginEnvironmentLease | null = null;\n      try {","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/plugin.ts#L101-L137","documentation":"Thrown in release() when a lease is being released without destroy while config.reuseLease is enabled, but a previous cleanup of this lease's process was never confirmed (the scope is in the 'unconfirmedCleanup' set). The provider refuses to pause/reuse a sandbox whose prior process termination could not be verified, because reusing it could attach to a stale or still-running process. Destroying the lease clears the flag.","triggerScenarios":"Releasing a reusable lease after a prior release ended without confirmed process cleanup (e.g. an earlier pause returned 404-or-unknown state, or a transition failed mid-flight), then attempting the default non-destroy release path with reuseLease: true.","commonSituations":"Reuse-mode pools where a sandbox was force-stopped at the host level so the later pause couldn't confirm in-sandbox process termination; a crashed earlier release that marked cleanup unconfirmed; operator toggled reuseLease on for leases that already had unconfirmed cleanup.","solutions":["Destroy the lease instead of reusing it: call release with destroy=true (or client.destroySandbox) — this clears the unconfirmedCleanup flag.","If reuse is not needed, set config.reuseLease to false so releases always destroy and never hit this path.","Investigate why the earlier cleanup was unconfirmed (network errors, unexpected CreateOS API responses) before relying on reuse for these leases.","If the flag is stale and the sandbox is verifiably clean, destroy-and-recreate is the only supported reset; there is no API to clear the flag without destroy."],"exampleFix":"// before: reuse release fails\nawait release({ ...params, leaseMetadata }, false);\n// after: destroy to clear unconfirmed cleanup state\nawait release({ ...params, leaseMetadata }, true);","handlingStrategy":"fallback","validationCode":"// Destroy leases with unconfirmed cleanup instead of reusing\nif (leaseHadUnconfirmedCleanup(scope)) {\n  await release({ ...params }, true); // destroy=true path\n} else {\n  await release({ ...params }, false);\n}","typeGuard":"function isUnconfirmedCleanupError(e: unknown): boolean {\n  return e instanceof Error && e.message.includes('process cleanup is unconfirmed');\n}","tryCatchPattern":"try {\n  await release(params, false); // try reuse\n} catch (e) {\n  if (isUnconfirmedCleanupError(e)) {\n    await release(params, true); // fall back to destroy\n  } else throw e;\n}","preventionTips":["Investigate the root cause of unconfirmed cleanups (CreateOS API errors) rather than just retrying","Disable reuseLease if your workloads cannot tolerate destroy-and-recreate","Treat unconfirmed-cleanup leases as poisoned and always destroy them","Log and alert on this error to catch host-level force-stops of sandboxes"],"tags":["sandbox","cleanup","lease-reuse","state"],"backgroundTag":"invalid-state-transition","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"}