{"record":{"id":"c9104ef2fcfc1ae0","repo":"paperclipai/paperclip","slug":"a-reusable-lease-handoff-requires-an-execution-wor","errorCode":null,"errorMessage":"A reusable lease handoff requires an execution workspace and provider lease id.","messagePattern":"A reusable lease handoff requires an execution workspace and provider lease id\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/environments.ts","lineNumber":1390,"sourceCode":"        status: \"active\" as const,\n        leasePolicy: input.leasePolicy ?? \"ephemeral\",\n        provider: input.provider ?? null,\n        providerLeaseId: input.providerLeaseId ?? null,\n        acquiredAt: now,\n        lastUsedAt: now,\n        expiresAt: input.expiresAt ?? null,\n        releasedAt: null,\n        failureReason: null,\n        cleanupStatus: null,\n        metadata: input.metadata ?? null,\n        createdAt: now,\n        updatedAt: now,\n      };\n      if (\n        (input.replacesReusableLeaseId || input.reusesReusableLeaseId) &&\n        (!input.executionWorkspaceId || !input.providerLeaseId)\n      ) {\n        throw new Error(\n          \"A reusable lease handoff requires an execution workspace and provider lease id.\",\n        );\n      }\n      if (input.reusesReusableLeaseId && !input.heartbeatRunId) {\n        throw new Error(\n          \"A same-run reusable lease reacquisition requires a heartbeat run id.\",\n        );\n      }\n      if (input.replacesReusableLeaseId && input.reusesReusableLeaseId) {\n        throw new Error(\n          \"A reusable lease cannot be replaced and reacquired in the same operation.\",\n        );\n      }\n      const row =\n        input.assertCompanyBinding ||\n        input.replacesReusableLeaseId ||\n        input.reusesReusableLeaseId\n          ? await db.transaction(async (tx) => {","sourceCodeStart":1372,"sourceCodeEnd":1408,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/environments.ts#L1372-L1408","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Populate both executionWorkspaceId and providerLeaseId whenever replacesReusableLeaseId or reusesReusableLeaseId is passed","Fix the caller to only set the handoff flags when a real provider lease exists (check provider response before calling)","If no handoff is intended, omit replacesReusableLeaseId/reusesReusableLeaseId entirely","Backfill the missing workspace/provider lease ids from the provider API before retrying"],"exampleFix":"// before\nacquireLease({ replacesReusableLeaseId: prevId })\n// after\nacquireLease({ replacesReusableLeaseId: prevId, executionWorkspaceId: ws.id, providerLeaseId: lease.providerLeaseId })","handlingStrategy":"validation","validationCode":"if ((input.replacesReusableLeaseId || input.reusesReusableLeaseId) && (!input.executionWorkspaceId || !input.providerLeaseId)) {\n  throw new Error('handoff requires executionWorkspaceId and providerLeaseId');\n}\nawait acquireLease(input);","typeGuard":"function isCompleteLeaseHandoff(i: {replacesReusableLeaseId?:string;reusesReusableLeaseId?:string;executionWorkspaceId?:string|null;providerLeaseId?:string|null}): boolean {\n  return !(i.replacesReusableLeaseId || i.reusesReusableLeaseId) || Boolean(i.executionWorkspaceId && i.providerLeaseId);\n}","tryCatchPattern":"try {\n  await environmentService.acquireReusableLease(input);\n} catch (e) {\n  if (e.message.includes('handoff requires')) {\n    const providerLease = await provider.createLease(); // fetch missing ids then retry\n    return environmentService.acquireReusableLease({ ...input, executionWorkspaceId: ws.id, providerLeaseId: providerLease.id });\n  }\n  throw e;\n}","preventionTips":["Only set handoff flags after the provider successfully returned a lease id","Validate lease inputs with a shared schema (zod) before calling environmentService","Backfill executionWorkspaceId from the workspace record when absent","Add tests covering every combination of replaces/reuses flags"],"tags":["leases","validation","environments"],"backgroundTag":"missing-required-argument","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}