paperclipai/paperclip · error · Error

Daytona template capture requires @daytonaio/sdk Sandbox._ex

Error message

Daytona template capture requires @daytonaio/sdk Sandbox._experimental_createSnapshot support.

What it means

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.

Source

Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:2255

  async onEnvironmentCaptureTemplate(
    params: PluginEnvironmentCaptureTemplateParams,
  ): Promise<PluginEnvironmentCaptureTemplateResult> {
    const config = parseDriverConfig(params.config);
    if (!params.providerLeaseId) {
      throw new Error("Cannot capture a Daytona template without a setup sandbox lease.");
    }
    const scope = {
      driverKey: params.driverKey,
      companyId: params.companyId,
      environmentId: params.environmentId,
      providerLeaseId: params.providerLeaseId,
      config,
    };
    return await withSandboxActivityGate(scope, async () => {
      const sandbox = await getSandbox(scope, { bypassTeardownGate: true });
    const createSnapshot = (sandbox as DaytonaInteractiveSandbox)._experimental_createSnapshot;
    if (typeof createSnapshot !== "function") {
      throw new Error(
        "Daytona template capture requires @daytonaio/sdk Sandbox._experimental_createSnapshot support.",
      );
    }
    const templateRef = sanitizeSnapshotName(
      params.templateLabel,
      `paperclip-${params.environmentId}-${randomUUID().slice(0, 8)}`,
    );
    const timeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0
      ? Math.trunc(params.timeoutMs)
      : config.timeoutMs;

      await createSnapshot.call(sandbox, templateRef, toTimeoutSeconds(timeoutMs));

      return {
        templateKind: "snapshot",
        templateRef,
        metadata: {
          provider: "daytona",

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. 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.
  2. Verify the capture is being called against a lease returned by startInteractiveSetup so the handle is a DaytonaInteractiveSandbox, not a bare client.get result.
  3. 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.
  4. Temporarily disable template capture for this provider (do not advertise environmentCaptureTemplate) until the SDK stabilizes the snapshot API.

Example fix

// before (package.json)
"@daytonaio/sdk": "0.210.0"

// after
"@daytonaio/sdk": "0.203.0"
Defensive patterns

Strategy: type-guard

Validate before calling

import type { Sandbox } from '@daytonaio/sdk';

type WithSnapshot = Sandbox & {
  _experimental_createSnapshot?: (n: string, t?: number) => Promise<void>;
};

async function safeCapture(
  sandbox: WithSnapshot,
  name: string,
  timeoutSeconds: number,
): Promise<boolean> {
  if (typeof sandbox._experimental_createSnapshot !== 'function') {
    return false; // caller skips onEnvironmentCaptureTemplate
  }
  await sandbox._experimental_createSnapshot(name, timeoutSeconds);
  return true;
}

Type guard

function supportsCreateSnapshot(
  s: Sandbox & { _experimental_createSnapshot?: unknown },
): s is Sandbox & {
  _experimental_createSnapshot: (name: string, timeout?: number) => Promise<void>;
} {
  return typeof (s as { _experimental_createSnapshot?: unknown })._experimental_createSnapshot === 'function';
}

Try / catch

try {
  await plugin.onEnvironmentCaptureTemplate(params);
} catch (err) {
  if (err instanceof Error && /_experimental_createSnapshot/.test(err.message)) {
    // SDK lacks the method: skip capture, do not crash the run
    return { templateKind: null, templateRef: null };
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/fcc694c4b7d56e06. Report an issue: GitHub.