paperclipai/paperclip · error

A reusable lease handoff requires an execution workspace and

Error message

A reusable lease handoff requires an execution workspace and provider lease id.

What it means

environmentService's lease lifecycle validation: when an operation hands off a reusable lease (replacesReusableLeaseId or reusesReusableLeaseId is set), the input must also carry executionWorkspaceId and providerLeaseId. It throws when a handoff/reacquisition is requested without the workspace and provider lease identifiers needed to bind the new lease.

Source

Thrown at server/src/services/environments.ts:1390

        status: "active" as const,
        leasePolicy: input.leasePolicy ?? "ephemeral",
        provider: input.provider ?? null,
        providerLeaseId: input.providerLeaseId ?? null,
        acquiredAt: now,
        lastUsedAt: now,
        expiresAt: input.expiresAt ?? null,
        releasedAt: null,
        failureReason: null,
        cleanupStatus: null,
        metadata: input.metadata ?? null,
        createdAt: now,
        updatedAt: now,
      };
      if (
        (input.replacesReusableLeaseId || input.reusesReusableLeaseId) &&
        (!input.executionWorkspaceId || !input.providerLeaseId)
      ) {
        throw new Error(
          "A reusable lease handoff requires an execution workspace and provider lease id.",
        );
      }
      if (input.reusesReusableLeaseId && !input.heartbeatRunId) {
        throw new Error(
          "A same-run reusable lease reacquisition requires a heartbeat run id.",
        );
      }
      if (input.replacesReusableLeaseId && input.reusesReusableLeaseId) {
        throw new Error(
          "A reusable lease cannot be replaced and reacquired in the same operation.",
        );
      }
      const row =
        input.assertCompanyBinding ||
        input.replacesReusableLeaseId ||
        input.reusesReusableLeaseId
          ? await db.transaction(async (tx) => {

View on GitHub (pinned to 01ad858492)

Solutions

  1. Populate both executionWorkspaceId and providerLeaseId whenever replacesReusableLeaseId or reusesReusableLeaseId is passed
  2. Fix the caller to only set the handoff flags when a real provider lease exists (check provider response before calling)
  3. If no handoff is intended, omit replacesReusableLeaseId/reusesReusableLeaseId entirely
  4. Backfill the missing workspace/provider lease ids from the provider API before retrying

Example fix

// before
acquireLease({ replacesReusableLeaseId: prevId })
// after
acquireLease({ replacesReusableLeaseId: prevId, executionWorkspaceId: ws.id, providerLeaseId: lease.providerLeaseId })
Defensive patterns

Strategy: validation

Validate before calling

if ((input.replacesReusableLeaseId || input.reusesReusableLeaseId) && (!input.executionWorkspaceId || !input.providerLeaseId)) {
  throw new Error('handoff requires executionWorkspaceId and providerLeaseId');
}
await acquireLease(input);

Type guard

function isCompleteLeaseHandoff(i: {replacesReusableLeaseId?:string;reusesReusableLeaseId?:string;executionWorkspaceId?:string|null;providerLeaseId?:string|null}): boolean {
  return !(i.replacesReusableLeaseId || i.reusesReusableLeaseId) || Boolean(i.executionWorkspaceId && i.providerLeaseId);
}

Try / catch

try {
  await environmentService.acquireReusableLease(input);
} catch (e) {
  if (e.message.includes('handoff requires')) {
    const providerLease = await provider.createLease(); // fetch missing ids then retry
    return environmentService.acquireReusableLease({ ...input, executionWorkspaceId: ws.id, providerLeaseId: providerLease.id });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the lease acquire/replace path with replacesReusableLeaseId or reusesReusableLeaseId set but executionWorkspaceId or providerLeaseId null/undefined (e.g. seedReusablePluginSandboxLease or reusableLease callers omitting fields).

Common situations: Partial input construction after a refactor added the handoff fields; a caller migrating from non-reusable to reusable leases without provider lease ids; provider failed to return a lease id but the flow continued; DB row missing executionWorkspaceId.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/c9104ef2fcfc1ae0. Report an issue: GitHub.