paperclipai/paperclip · error
CreateOS execution requires a lease from this environment.
Error message
CreateOS execution requires a lease from this environment.
What it means
track() guards every sandbox operation (execute, sync in/out) with a lease-ownership check: the lease must have a providerLeaseId and its metadata must match the calling environment (provider 'createos', same companyId, environmentId, and apiUrl derived from the current config). This error is thrown when the lease passed to an operation was created by a different environment, company, or API URL, or has no providerLeaseId at all.
Solutions
- Release and re-acquire the lease under the current environment/config so its metadata is regenerated to match.
- Confirm the lease's metadata fields (provider, companyId, environmentId, apiUrl) match the params used for the execute call.
- If you changed apiUrl or connection config, drop stale persisted leases and re-probe the environment.
- Check that the host is not swapping or truncating lease.metadata when deserializing persisted run state.
Example fix
// before
const result = await driver.execute({ ...params, lease: leaseFromOtherEnv });
// after: verify ownership before executing
if (lease.metadata?.companyId !== params.companyId || lease.metadata?.environmentId !== params.environmentId) {
lease = await acquireLease(params); // re-acquire for this environment
}
const result = await driver.execute({ ...params, lease }); Defensive patterns
Strategy: validation
Validate before calling
function canUseLease(lease, params, apiUrl) {
const m = lease?.metadata;
return Boolean(lease?.providerLeaseId) && m?.provider === "createos" &&
m?.companyId === params.companyId && m?.environmentId === params.environmentId &&
m?.apiUrl === apiUrl;
} Type guard
function isUsableCreateosLease(l): l is PluginEnvironmentLease & { providerLeaseId: string } {
return typeof (l as any)?.providerLeaseId === "string" && (l as any).providerLeaseId.length > 0 &&
(l as any)?.metadata?.provider === "createos";
} Try / catch
try {
return await driver.execute({ ...params, lease });
} catch (e) {
if (e.message.includes("requires a lease from this environment")) {
lease = await driver.acquireLease(params); // re-acquire matching lease
return await driver.execute({ ...params, lease });
}
throw e;
} Prevention
- Always propagate lease.metadata verbatim from acquire to subsequent calls.
- Re-acquire leases whenever connection config (apiUrl) changes.
- Never share lease objects across environments or companies.
- Validate lease ownership before execute in the host layer with canUseLease().
When it happens
Trigger: onEnvironmentExecute (or sync) is called with params.lease whose metadata is undefined, whose metadata.provider !== 'createos', whose companyId/environmentId differ from params, or whose metadata.apiUrl differs from the apiUrl parsed from the current config; or params.lease.providerLeaseId is null/empty.
Common situations: Changing the CreateOS apiUrl (or shape/rootfs/region settings that feed apiUrl resolution) after a lease was acquired, so old lease metadata no longer matches config; passing a lease from environment A into an execute call for environment B; a host bug persisting/stripping lease metadata across process restarts; accidentally passing a non-CreateOS lease object.
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 lease does not belong to this environment.
- CreateOS does not yet support leases with a guaranteed…
- CreateOS lease cleanup must finish before resume.
- CreateOS lease is closing or requires cleanup.
- CreateOS process creation could not be confirmed; destroy…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/94969a1a094d5f69.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/plugin.ts:80
function key(params: PluginEnvironmentDriverBaseParams, id: string): string {
const config = parseConfig(params.config);
const account = createHash("sha256").update(resolveApiKey(config)).digest("hex");
return JSON.stringify([params.companyId, params.environmentId, config.apiUrl, account, id]);
}
async function stopActive(scope: string) {
const calls = [...(active.get(scope) ?? [])];
for (const call of calls) call.controller.abort();
await Promise.all(calls.map((call) => call.done));
}
async function track<T>(
params: PluginEnvironmentDriverBaseParams & { lease: PluginEnvironmentLease },
work: (client: CreateosClient, signal: AbortSignal) => Promise<T>,
timeoutOverride?: number,
): Promise<T> {
if (!params.lease.providerLeaseId || !metadataMatches(params, params.lease.metadata)) throw new Error("CreateOS execution requires a lease from this environment.");
const scope = key(params, params.lease.providerLeaseId);
if (shuttingDown || closing.has(scope) || unconfirmedCleanup.has(scope)) throw new Error("CreateOS lease is closing or requires cleanup.");
const config = parseConfig(params.config);
const timeoutMs = timeoutOverride ?? config.timeoutMs;
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86_400_000) throw new Error("Invalid CreateOS command timeout.");
const controller = new AbortController();
let finish!: () => void;
const entry: Active = { controller, done: new Promise<void>((resolve) => { finish = resolve; }) };
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);View on GitHub (pinned to 3f1d897a7c)