{"record":{"id":"fcc694c4b7d56e06","repo":"paperclipai/paperclip","slug":"daytona-template-capture-requires-daytonaio-sdk-s","errorCode":null,"errorMessage":"Daytona template capture requires @daytonaio/sdk Sandbox._experimental_createSnapshot support.","messagePattern":"Daytona template capture requires @daytonaio/sdk Sandbox\\._experimental_createSnapshot support\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/daytona/src/plugin.ts","lineNumber":2255,"sourceCode":"  async onEnvironmentCaptureTemplate(\n    params: PluginEnvironmentCaptureTemplateParams,\n  ): Promise<PluginEnvironmentCaptureTemplateResult> {\n    const config = parseDriverConfig(params.config);\n    if (!params.providerLeaseId) {\n      throw new Error(\"Cannot capture a Daytona template without a setup sandbox lease.\");\n    }\n    const scope = {\n      driverKey: params.driverKey,\n      companyId: params.companyId,\n      environmentId: params.environmentId,\n      providerLeaseId: params.providerLeaseId,\n      config,\n    };\n    return await withSandboxActivityGate(scope, async () => {\n      const sandbox = await getSandbox(scope, { bypassTeardownGate: true });\n    const createSnapshot = (sandbox as DaytonaInteractiveSandbox)._experimental_createSnapshot;\n    if (typeof createSnapshot !== \"function\") {\n      throw new Error(\n        \"Daytona template capture requires @daytonaio/sdk Sandbox._experimental_createSnapshot support.\",\n      );\n    }\n    const templateRef = sanitizeSnapshotName(\n      params.templateLabel,\n      `paperclip-${params.environmentId}-${randomUUID().slice(0, 8)}`,\n    );\n    const timeoutMs = typeof params.timeoutMs === \"number\" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0\n      ? Math.trunc(params.timeoutMs)\n      : config.timeoutMs;\n\n      await createSnapshot.call(sandbox, templateRef, toTimeoutSeconds(timeoutMs));\n\n      return {\n        templateKind: \"snapshot\",\n        templateRef,\n        metadata: {\n          provider: \"daytona\",","sourceCodeStart":2237,"sourceCodeEnd":2273,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/sandbox-providers/daytona/src/plugin.ts#L2237-L2273","documentation":"Thrown by the Daytona plugin's onEnvironmentCaptureTemplate hook after it resolves the setup sandbox. The plugin reads the optional Sandbox._experimental_createSnapshot method (typed optional on DaytonaInteractiveSandbox) and refuses to capture a template when the resolved sandbox instance does not expose it, rather than calling undefined. Because the method is namespaced _experimental_*, its presence depends on the installed @daytonaio/sdk version (currently pinned to 0.203.0) and on the sandbox having been produced through the interactive-setup code path.","triggerScenarios":"Calling onEnvironmentCaptureTemplate (template capture from a finished interactive setup) when (a) @daytonaio/sdk has been bumped to a version that renamed or removed _experimental_createSnapshot, (b) the cached handle was repopulated by a plain client.get and its prototype lacks the experimental method, or (c) a test/mock sandbox object does not stub _experimental_createSnapshot.","commonSituations":"SDK upgrade/downgrade churn while the method is still experimental; CI or unit runs using a stub Sandbox that only implements stable methods; attempting to capture a template from a lease that was not created via startInteractiveSetup.","solutions":["Pin or restore @daytonaio/sdk to a version that exposes Sandbox._experimental_createSnapshot (the repo currently pins 0.203.0); run pnpm install and rebuild the daytona provider.","Verify the capture is being called against a lease returned by startInteractiveSetup so the handle is a DaytonaInteractiveSandbox, not a bare client.get result.","If upgrading the SDK, re-check the DaytonaInteractiveSandbox type at plugin.ts:142 and update the method name / signature to match the new SDK before re-enabling capture.","Temporarily disable template capture for this provider (do not advertise environmentCaptureTemplate) until the SDK stabilizes the snapshot API."],"exampleFix":"// before (package.json)\n\"@daytonaio/sdk\": \"0.210.0\"\n\n// after\n\"@daytonaio/sdk\": \"0.203.0\"","handlingStrategy":"type-guard","validationCode":"import type { Sandbox } from '@daytonaio/sdk';\n\ntype WithSnapshot = Sandbox & {\n  _experimental_createSnapshot?: (n: string, t?: number) => Promise<void>;\n};\n\nasync function safeCapture(\n  sandbox: WithSnapshot,\n  name: string,\n  timeoutSeconds: number,\n): Promise<boolean> {\n  if (typeof sandbox._experimental_createSnapshot !== 'function') {\n    return false; // caller skips onEnvironmentCaptureTemplate\n  }\n  await sandbox._experimental_createSnapshot(name, timeoutSeconds);\n  return true;\n}","typeGuard":"function supportsCreateSnapshot(\n  s: Sandbox & { _experimental_createSnapshot?: unknown },\n): s is Sandbox & {\n  _experimental_createSnapshot: (name: string, timeout?: number) => Promise<void>;\n} {\n  return typeof (s as { _experimental_createSnapshot?: unknown })._experimental_createSnapshot === 'function';\n}","tryCatchPattern":"try {\n  await plugin.onEnvironmentCaptureTemplate(params);\n} catch (err) {\n  if (err instanceof Error && /_experimental_createSnapshot/.test(err.message)) {\n    // SDK lacks the method: skip capture, do not crash the run\n    return { templateKind: null, templateRef: null };\n  }\n  throw err;\n}","preventionTips":["Pin @daytonaio/sdk (the repo pins 0.203.0) and lockfile-verify before deploy.","Advertise environmentCaptureTemplate only when the loaded SDK exposes the method; gate the capability on a startup probe.","In tests, always stub _experimental_createSnapshot on the mock sandbox."],"tags":["daytona","sdk-version","sandbox","template","capability-check","experimental-api"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}