paperclipai/paperclip · error

CreateOS process cleanup is unconfirmed; destroy this lease…

Error message

CreateOS process cleanup is unconfirmed; destroy this lease before reusing it.

What it means

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.

Solutions

  1. Destroy the lease instead of reusing it: call release with destroy=true (or client.destroySandbox) — this clears the unconfirmedCleanup flag.
  2. If reuse is not needed, set config.reuseLease to false so releases always destroy and never hit this path.
  3. Investigate why the earlier cleanup was unconfirmed (network errors, unexpected CreateOS API responses) before relying on reuse for these leases.
  4. 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.

Example fix

// before: reuse release fails
await release({ ...params, leaseMetadata }, false);
// after: destroy to clear unconfirmed cleanup state
await release({ ...params, leaseMetadata }, true);
Defensive patterns

Strategy: fallback

Validate before calling

// Destroy leases with unconfirmed cleanup instead of reusing
if (leaseHadUnconfirmedCleanup(scope)) {
  await release({ ...params }, true); // destroy=true path
} else {
  await release({ ...params }, false);
}

Type guard

function isUnconfirmedCleanupError(e: unknown): boolean {
  return e instanceof Error && e.message.includes('process cleanup is unconfirmed');
}

Try / catch

try {
  await release(params, false); // try reuse
} catch (e) {
  if (isUnconfirmedCleanupError(e)) {
    await release(params, true); // fall back to destroy
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/6aa90ab4aee42b02. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/plugin.ts:119

    }
  }

  async function release(params: PluginEnvironmentReleaseLeaseParams, destroy: boolean) {
    const id = params.providerLeaseId;
    if (!id) return;
    if (!metadataMatches(params, params.leaseMetadata)) throw new Error("CreateOS lease does not belong to this environment.");
    const scope = key(params, id);
    if (closing.has(scope)) throw new Error("CreateOS lease cleanup is already in progress.");
    closing.add(scope);
    try {
      await stopActive(scope);
      const config = parseConfig(params.config);
      const client = new CreateosClient(config);
      if (destroy || !config.reuseLease) {
        await client.destroySandbox(id);
        unconfirmedCleanup.delete(scope);
      } else {
        if (unconfirmedCleanup.has(scope)) throw new Error("CreateOS process cleanup is unconfirmed; destroy this lease before reusing it.");
        try { await client.transition(id, "paused", AbortSignal.timeout(config.timeoutMs)); }
        catch (error) { if (!(error instanceof CreateosApiError && error.status === 404)) throw error; }
      }
    } finally {
      closing.delete(scope);
    }
  }

  return definePlugin({
    async setup(context) { ctx = context; ctx.logger.info("CreateOS sandbox provider ready"); },
    async onHealth() { return { status: "ok", message: "CreateOS provider loaded; probe an environment to check connectivity." }; },
    async onEnvironmentValidateConfig(params) {
      try { return { ok: true, normalizedConfig: { ...parseConfig(params.config) } }; }
      catch (error) { return { ok: false, errors: [error instanceof Error ? error.message : "Invalid CreateOS configuration."] }; }
    },
    async onEnvironmentProbe(params) {
      let lease: PluginEnvironmentLease | null = null;
      try {

View on GitHub (pinned to 3f1d897a7c)