paperclipai/paperclip · error

Daytona sandbox handle mismatch: handle

Error message

Daytona sandbox handle mismatch: handle ${sandbox.id} does not belong to lease ${providerLeaseId}.

What it means

Belt-and-suspenders identity check (C2) in assertHandleMatchesLease: the sandbox handle the provider returned (sandbox.id) does not match the lease's providerLeaseId, suggesting a renamed or substituted sandbox. The driver refuses to use a handle that stands in for a different sandbox than the lease asked for, preventing cross-lease sandbox confusion.

Solutions

  1. Use the sandbox handle that belongs to the lease; re-acquire the sandbox for the lease.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at packages/plugins/sandbox-providers/daytona/src/plugin.ts:956 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-08-18). Data as JSON: /api/errors/794dcc4c2b946078. Report an issue: GitHub.

Appendix: source

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

  const resolvedApiKey = config.apiKey ?? process.env.DAYTONA_API_KEY?.trim() ?? null;
  return createHash("sha256")
    .update(stableStringify({
      apiUrl: config.apiUrl,
      // Target is a creation placement hint, not account identity: the SDK
      // resolves existing sandboxes by ID. Lease metadata fills an omitted
      // target with the actual region, which must not split admission state.
      apiKey: resolvedApiKey,
    }))
    .digest("hex");
}

function sandboxHandleCacheKey(scope: SandboxScope): string {
  return stableStringify({
    driverKey: scope.driverKey,
    companyId: scope.companyId,
    environmentId: scope.environmentId,
    providerLeaseId: scope.providerLeaseId,
    account: sandboxAccountDiscriminator(scope.config),
  });
}

function assertHandleMatchesLease(sandbox: Sandbox, providerLeaseId: string): void {
  // C2: a handle must never stand in for a different sandbox than the lease
  // asked for. Belt-and-suspenders against a provider that returns a renamed or
  // substituted sandbox, and against any future key collision.
  if (sandbox.id !== providerLeaseId) {
    throw new Error(
      `Daytona sandbox handle mismatch: handle ${sandbox.id} does not belong to lease ${providerLeaseId}.`,
    );
  }
}

// A cached `Sandbox` carries the provider state captured when it was last
// fetched/refreshed. Daytona auto-stops an idle sandbox after `autoStopInterval`
// minutes, at which point that snapshot ("started") no longer matches reality
// and `ensureSandboxStarted` would wrongly skip the restart, sending every

View on GitHub (pinned to 3f1d897a7c)