paperclipai/paperclip · error
CreateOS lease does not belong to this environment.
Error message
CreateOS lease does not belong to this environment.
What it means
release() verifies that the lease being released (or destroyed) actually belongs to the calling environment by checking leaseMetadata against params (provider 'createos', same companyId, environmentId, and apiUrl from current config). This error is thrown when releasing a lease whose metadata does not match the environment issuing the release, preventing cross-environment destruction of sandboxes.
Solutions
- Pass the original lease.metadata unchanged as leaseMetadata when releasing — do not reconstruct or drop fields.
- If config changed, release using the config values that were active when the lease was acquired, or destroy the sandbox directly in the CreateOS dashboard.
- Verify companyId/environmentId in the release params match those captured in the lease metadata.
- Re-acquire a fresh lease under current config and discard the stale one if metadata reconciliation is impossible.
Example fix
// before: metadata dropped when persisting the lease
await driver.releaseLease({ companyId, environmentId, providerLeaseId: id, leaseMetadata: undefined });
// after: persist and replay the full metadata
await driver.releaseLease({
companyId, environmentId,
providerLeaseId: lease.providerLeaseId,
leaseMetadata: lease.metadata, // unchanged from acquire
}); Defensive patterns
Strategy: validation
Validate before calling
function canRelease(params) {
const m = params.leaseMetadata;
return Boolean(m) && m.provider === "createos" &&
m.companyId === params.companyId && m.environmentId === params.environmentId &&
m.apiUrl === currentConfig.apiUrl;
}
if (!canRelease(releaseParams)) console.warn("lease metadata mismatch; release will be rejected"); Type guard
function hasMatchingLeaseMetadata(p): p is typeof p & { leaseMetadata: Record<string, unknown> } {
const m = (p as any)?.leaseMetadata;
return Boolean(m) && m.provider === "createos" && m.companyId === p.companyId && m.environmentId === p.environmentId;
} Try / catch
try {
await driver.destroyLease(releaseParams);
} catch (e) {
if (e.message.includes("does not belong to this environment")) {
logger.warn(`stale lease ${releaseParams.providerLeaseId}; destroy manually in CreateOS dashboard`);
} else throw e;
} Prevention
- Persist lease.metadata alongside providerLeaseId and replay it exactly on release.
- Snapshot the apiUrl used at acquire time with the lease so stale leases can be released with their original config.
- Never hand-build release params from partial data; always carry through what acquire returned.
- On metadata mismatch, prefer dashboard/API-side destruction over blind re-acquisition to avoid orphaned sandboxes.
When it happens
Trigger: onEnvironmentReleaseLease or onEnvironmentDestroyLease is called with params.providerLeaseId set but params.leaseMetadata missing, not provider 'createos', or with companyId/environmentId/apiUrl differing from params; commonly after config (apiUrl) changed since the lease was created.
Common situations: Releasing a persisted lease after the CreateOS connection's apiUrl was edited; host cleanup job releasing leases from environment A under environment B's credentials; passing null/undefined leaseMetadata because the lease record was restored from an old schema version; manually constructing release params without copying the original metadata.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- CreateOS execution requires a lease from this environment.
- CreateOS lease cleanup must finish before resume.
- CreateOS process creation could not be confirmed; destroy…
- Could not clean managed GitHub launchers
- CreateOS command cleanup failed; process termination is…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/b57261444f80ea47.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/plugin.ts:107
const calls = active.get(scope) ?? new Set<Active>();
calls.add(entry);
active.set(scope, calls);
try {
return await work(new CreateosClient(config), AbortSignal.any([controller.signal, AbortSignal.timeout(timeoutMs)]));
} catch (error) {
if (error instanceof CreateosCleanupError) unconfirmedCleanup.add(scope);
throw error;
} finally {
calls.delete(entry);
if (calls.size === 0) active.delete(scope);
finish();
}
}
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);
}View on GitHub (pinned to 3f1d897a7c)