paperclipai/paperclip · error · SandboxOrphanCleanupWriteError

Sandbox provider " " leaked lease " ": the acquire could…

Error message

Sandbox provider "${input.provider}" leaked lease "${input.providerLeaseId ?? "unknown"}": the acquire could not tear it down and could not record a durable pending-cleanup row. The live sandbox has no durable cleanup state and needs a manual teardown.

What it means

Error "Sandbox provider "${input.provider}" leaked lease "${input.providerLeaseId ?? "unknown"}": the acquire could not tear it down and could not record a durable pending-cleanup row. The live sandbox has no durable cleanup state and needs a manual teardown." thrown in paperclipai/paperclip.

Solutions

  1. Manually tear down the leaked sandbox using the provider lease id in the message, then repair the cleanup row state.
Defensive patterns

Strategy: fallback

When it happens

Trigger: Thrown at server/src/services/environment-runtime.ts:1259 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at server/src/services/environment-runtime.ts:1385

          issueId: record.issueId,
          heartbeatRunId: record.heartbeatRunId,
          provider: record.provider,
          providerLeaseId: record.providerLeaseId,
          metadata: record.metadata,
          failureReason: DEFERRED_ORPHAN_CLEANUP_FAILURE_REASON,
        });
        // The durable row exists now, so the sweep finds and releases the orphan.
        // Drop the spooled copy only after the row lands. A crash between the two
        // leaves the spooled copy for a later flush, which re-inserts a duplicate
        // the idempotent teardown handles. This order never loses the orphan.
        await orphanCleanupSpool.remove(record);
        recovered += 1;
      } catch {
        // The database is still down. Re-queue the record for a later flush,
        // unless the buffer filled again. The identifiers carry no secret; the
        // caught write exception never enters the log, because it can hold a
        // credential in its message, code, cause, or stack.
        const requeued = enqueueDeferredOrphanCleanup(record);
        logger.warn(
          {
            errorKind: SANDBOX_ORPHAN_CLEANUP_WRITE_ERROR_KIND,
            provider: record.provider,
            providerLeaseId: record.providerLeaseId,
            companyId: record.companyId,
            environmentId: record.environmentId,
            requeued,
          },
          requeued
            ? "deferred sandbox orphan cleanup flush failed; kept the orphan buffered for a later flush"
            : "deferred sandbox orphan cleanup flush failed and the in-process buffer is full; live sandbox needs a manual teardown",
        );
      }
    }
    return { recovered, pending: deferredOrphanCleanups.length };
  };

View on GitHub (pinned to 3f1d897a7c)