paperclipai/paperclip · error
CreateOS workspace requires a lease from this environment.
Error message
CreateOS workspace requires a lease from this environment.
What it means
The CreateOS sandbox provider plugin throws this when a workspace realization request arrives with a lease that does not belong to this environment: either the lease has no providerLeaseId or the lease metadata no longer matches the environment's recorded metadata. It exists to prevent realizing a workspace from a stale or foreign lease, which could stage runtime source mappings into the wrong sandbox context.
Solutions
- Re-acquire a fresh lease for this environment (onEnvironmentAcquireLease) and pass that lease to the realize call.
- Verify the lease you pass still has a non-empty providerLeaseId; if not, the lease expired or failed activation and must be re-created.
- Check that nothing mutated the lease.metadata since acquisition; restore the original metadata or re-acquire.
- Confirm you are not crossing environments: realize the workspace with the lease issued by the same environment instance.
Example fix
// before
await plugin.onEnvironmentRealizeWorkspace({ lease: staleLease });
// after
const lease = await plugin.onEnvironmentAcquireLease(params);
await plugin.onEnvironmentRealizeWorkspace({ ...params, lease }); Defensive patterns
Strategy: validation
Validate before calling
function canRealize(lease) {
return typeof lease?.providerLeaseId === 'string' && lease.providerLeaseId.length > 0 &&
lease.metadata != null;
}
if (!canRealize(lease)) lease = await acquireFreshLease(params); Type guard
const isValidLease = (l) => typeof l?.providerLeaseId === 'string' && l.providerLeaseId.length > 0 && typeof l?.metadata === 'object';
Try / catch
try {
await plugin.onEnvironmentRealizeWorkspace({ lease });
} catch (e) {
if (e.message.includes('requires a lease from this environment')) {
const fresh = await acquireLeaseForEnvironment(env);
await plugin.onEnvironmentRealizeWorkspace({ lease: fresh });
} else throw e;
} Prevention
- Always pass the lease object obtained from the same environment instance you realize against.
- Treat lease metadata as immutable after acquisition.
- Re-acquire leases after environment restarts instead of reusing cached ones.
When it happens
Trigger: Calling onEnvironmentRealizeWorkspace (via environment.workspace/realize paths) when params.lease.providerLeaseId is null/undefined, or when metadataMatches(params, params.lease.metadata) fails because the lease was created by a different environment or its metadata was mutated after lease creation.
Common situations: Reusing a lease object captured before an environment was recreated; a provider restart that rotated metadata; handing a lease from one environment to another; deserialized/cached lease state that lost its providerLeaseId.
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
- CreateOS does not yet support leases with a guaranteed…
- CreateOS execution requires a lease from this environment.
- 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/80ed7a8994d84c14.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/plugin.ts:183
const sandbox = await client.getSandbox(params.providerLeaseId, signal);
if (["destroyed", "failed"].includes(sandbox.status!)) return { providerLeaseId: null, metadata: { expired: true } };
await client.transition(params.providerLeaseId, "running", signal);
const data = await client.json(`/sandboxes/${params.providerLeaseId}/exec`, "POST", {
cmd: "/bin/cat", args: [MARKER],
}, signal);
const result = object(data.result);
if (result.exit_code !== 0 || result.stdout !== marker) return { providerLeaseId: null, metadata: { expired: true } };
return { providerLeaseId: params.providerLeaseId, metadata: { ...params.leaseMetadata, resumedLease: true } };
} catch (error) {
if (error instanceof CreateosApiError && error.status === 404) return { providerLeaseId: null, metadata: { expired: true } };
// A transient error does not prove the original sandbox is lost.
throw error;
}
},
onEnvironmentReleaseLease: (params) => release(params, false),
onEnvironmentDestroyLease: (params) => release(params, true),
async onEnvironmentRealizeWorkspace(params) {
if (!params.lease.providerLeaseId || !metadataMatches(params, params.lease.metadata)) throw new Error("CreateOS workspace requires a lease from this environment.");
// The runtime's source mappings stage into this provider workspace.
return { cwd: CWD, metadata: { provider: "createos", remoteCwd: CWD } };
},
async onEnvironmentExecute(params: PluginEnvironmentExecuteParams) {
return track(params, (client, signal) => execute(client, params, signal,
(stream, text) => ctx?.execution.log(stream, text)), params.timeoutMs);
},
onEnvironmentSyncIn: (params) => track(params, (client, signal) => syncFiles(client, params, "in", signal)),
onEnvironmentSyncOut: (params) => track(params, (client, signal) => syncFiles(client, params, "out", signal)),
async onShutdown() {
shuttingDown = true;
await Promise.all([...active.keys()].map(stopActive));
ctx = null;
},
});
}
export default createPlugin();View on GitHub (pinned to 3f1d897a7c)