{"record":{"id":"bca4599edffe3591","repo":"paperclipai/paperclip","slug":"daytona-template-deletion-requires-daytonaio-sdk","errorCode":null,"errorMessage":"Daytona template deletion requires @daytonaio/sdk snapshot.get/delete support.","messagePattern":"Daytona template deletion requires @daytonaio/sdk snapshot\\.get/delete support\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/daytona/src/plugin.ts","lineNumber":2350,"sourceCode":"      };\n    } finally {\n      sandboxHandleTeardownGates.end(scope, teardownGate);\n      evictSandboxHandle(scope);\n    }\n  },\n\n  async onEnvironmentDeleteTemplate(\n    params: PluginEnvironmentDeleteTemplateParams,\n  ): Promise<PluginEnvironmentDeleteTemplateResult> {\n    const templateKind = params.templateKind ?? \"snapshot\";\n    if (templateKind !== \"snapshot\") {\n      throw new Error(`Daytona can delete snapshot templates only, not ${templateKind}.`);\n    }\n    const config = parseDriverConfig(params.config);\n    const client = createDaytonaClient(config) as Daytona & { snapshot?: DaytonaSnapshotService };\n    const snapshotService = client.snapshot;\n    if (typeof snapshotService?.get !== \"function\" || typeof snapshotService.delete !== \"function\") {\n      throw new Error(\"Daytona template deletion requires @daytonaio/sdk snapshot.get/delete support.\");\n    }\n    const snapshot = await snapshotService.get(params.templateRef);\n    await snapshotService.delete(snapshot);\n    return {\n      deleted: true,\n      metadata: {\n        provider: \"daytona\",\n        templateKind: \"snapshot\",\n        templateRefRedacted: true,\n        reason: params.reason ?? null,\n      },\n    };\n  },\n\n  async onEnvironmentExecute(\n    params: PluginEnvironmentExecuteParams,\n  ): Promise<PluginEnvironmentExecuteResult> {\n    if (!params.lease.providerLeaseId) {","sourceCodeStart":2332,"sourceCodeEnd":2368,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/sandbox-providers/daytona/src/plugin.ts#L2332-L2368","documentation":"Thrown by onEnvironmentDeleteTemplate after the templateKind guard passes. The plugin casts the Daytona client to expose an optional snapshot service and requires both client.snapshot.get and client.snapshot.delete to be functions before deleting. It fires when the installed @daytonaio/sdk does not ship the snapshot service (or shipped it under a different shape), so deletion cannot be performed safely.","triggerScenarios":"Calling delete-template on a Daytona SDK version whose client has no snapshot service, or where snapshot.get / snapshot.delete are not functions; this is the delete-side analogue of the capture-side _experimental_createSnapshot capability check.","commonSituations":"SDK downgrade below the version that introduced the snapshot service; a custom/forked SDK build that omits the snapshot service; running delete-template in a test harness with a mock client that only stubs create.","solutions":["Upgrade @daytonaio/sdk to a version that exposes client.snapshot.get and client.snapshot.delete (the repo pins 0.203.0); rebuild the provider.","In tests, stub client.snapshot with both get and delete as vi.fn() before invoking delete-template.","If the SDK removed/renamed the service, update the DaytonaSnapshotService type at plugin.ts:147 and the cast at plugin.ts:2347 to match the new surface."],"exampleFix":"// before (test mock)\nconst client = { snapshot: {} };\n\n// after\nconst client = {\n  snapshot: { get: vi.fn(), delete: vi.fn() },\n};","handlingStrategy":"type-guard","validationCode":"function snapshotDeletionSupported(client: unknown): boolean {\n  const s = (client as { snapshot?: { get?: unknown; delete?: unknown } }).snapshot;\n  return typeof s?.get === 'function' && typeof s?.delete === 'function';\n}\n// if (!snapshotDeletionSupported(client)) skip delete-template for this SDK.","typeGuard":"type SnapshotService = {\n  get: (name: string) => Promise<unknown>;\n  delete: (snapshot: unknown) => Promise<void>;\n};\nfunction hasSnapshotService(\n  c: unknown,\n): c is { snapshot: SnapshotService } {\n  const s = (c as { snapshot?: Partial<SnapshotService> }).snapshot;\n  return !!s && typeof s.get === 'function' && typeof s.delete === 'function';\n}","tryCatchPattern":"try {\n  await plugin.onEnvironmentDeleteTemplate(params);\n} catch (err) {\n  if (err instanceof Error && /snapshot\\.get\\/delete support/.test(err.message)) {\n    // SDK cannot delete; leave the snapshot or fall back to manual cleanup\n    return { deleted: false };\n  }\n  throw err;\n}","preventionTips":["Run a startup capability probe against @daytonaio/sdk and advertise delete-template only when client.snapshot is present.","Pin the SDK version; re-run the provider's contract tests on any SDK bump."],"tags":["daytona","sdk-version","snapshot","delete-template","capability-check"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}