{"record":{"id":"33d1e97ea24e872e","repo":"paperclipai/paperclip","slug":"daytona-syncout-requires-a-provider-lease-id","errorCode":null,"errorMessage":"Daytona syncOut requires a provider lease ID.","messagePattern":"Daytona syncOut requires a provider lease ID\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/daytona/src/plugin.ts","lineNumber":2495,"sourceCode":"      const sandbox = await getSandbox(scope, { bypassTeardownGate: true });\n      await ensureSandboxStarted(sandbox, timeoutSeconds);\n      const result = await performSyncIn({\n        sandbox,\n        operations: params.operations,\n        remoteDir,\n        timeoutSeconds,\n      });\n      sandboxHandleCache.markFresh(scope);\n      return result;\n    });\n  },\n\n  // Opt-in native outbound transfer. See onEnvironmentSyncIn.\n  async onEnvironmentSyncOut(\n    params: PluginEnvironmentSyncOutParams,\n  ): Promise<PluginEnvironmentSyncResult> {\n    if (!params.lease.providerLeaseId) {\n      throw new Error(\"Daytona syncOut requires a provider lease ID.\");\n    }\n    const config = parseDriverConfig(params.config);\n    const remoteDir = resolveSyncRemoteDir(params.lease);\n    const timeoutSeconds = toTimeoutSeconds(config.timeoutMs);\n    const scope = {\n      driverKey: params.driverKey,\n      companyId: params.companyId,\n      environmentId: params.environmentId,\n      providerLeaseId: params.lease.providerLeaseId,\n      config,\n    };\n    return await withSandboxActivityGate(scope, async () => {\n      const sandbox = await getSandbox(scope, { bypassTeardownGate: true });\n      await ensureSandboxStarted(sandbox, timeoutSeconds);\n      const result = await performSyncOut({\n        sandbox,\n        operations: params.operations,\n        remoteDir,","sourceCodeStart":2477,"sourceCodeEnd":2513,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/sandbox-providers/daytona/src/plugin.ts#L2477-L2513","documentation":"Mirror of the syncIn guard for outbound transfer. onEnvironmentSyncOut throws at the top of the hook when params.lease.providerLeaseId is falsy, because downloading files from the sandbox requires resolving a specific live sandbox handle via its id.","triggerScenarios":"Calling onEnvironmentSyncOut with a lease missing providerLeaseId, e.g. after the sandbox was torn down, or before acquire/resume returned an id, or with a lease object built client-side without the id.","commonSituations":"Job epilogues that try to fetch artifacts after the lease was already released; a lease-rotation bug handing an empty lease to the outbound-transfer step; concurrent teardown racing with syncOut.","solutions":["Ensure the lease passed to syncOut is the same one returned by acquire/resume and still carries providerLeaseId.","Order teardown after syncOut completes; do not release/destroy the lease before artifact download.","Add a caller-side guard that skips syncOut when providerLeaseId is empty rather than invoking the hook."],"exampleFix":"// before\nawait plugin.onEnvironmentSyncOut({\n  ...params,\n  lease: { providerLeaseId: '' },\n});\n\n// after\nif (!lease.providerLeaseId) return skipSyncOut();\nawait plugin.onEnvironmentSyncOut({ ...params, lease });","handlingStrategy":"validation","validationCode":"function assertLeaseForSyncOut(lease: { providerLeaseId?: string | null }): string {\n  if (!lease.providerLeaseId) {\n    throw new Error('Cannot syncOut without a provider lease.');\n  }\n  return lease.providerLeaseId;\n}","typeGuard":"function hasProviderLease(\n  l: { providerLeaseId?: string | null },\n): l is { providerLeaseId: string } {\n  return typeof l.providerLeaseId === 'string' && l.providerLeaseId.length > 0;\n}","tryCatchPattern":"try {\n  await plugin.onEnvironmentSyncOut(params);\n} catch (err) {\n  if (err instanceof Error && /syncOut requires a provider lease ID/.test(err.message)) {\n    // lease was released mid-run; re-acquire, then retry once\n    return;\n  }\n  throw err;\n}","preventionTips":["Schedule syncOut before lease release/destroy in the run lifecycle.","Make teardown wait on in-flight sync operations via the activity gate."],"tags":["daytona","sync","lease","input-validation","sandbox"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}