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

  1. Re-acquire a fresh lease for this environment (onEnvironmentAcquireLease) and pass that lease to the realize call.
  2. Verify the lease you pass still has a non-empty providerLeaseId; if not, the lease expired or failed activation and must be re-created.
  3. Check that nothing mutated the lease.metadata since acquisition; restore the original metadata or re-acquire.
  4. 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

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


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)