Skip to content

Desktop JS: preserve allocation identity when startup cleanup also fails #1808

Description

@danielgwilson

Sandbox ID or Build ID

None. The reproduction uses a synthetic allocated object and makes no provider requests.

Environment

Desktop JavaScript source on E2B main, commit 473d8bf3e62b68ee731cf18afb2e8258f9ca7a7c, which includes the 2.3.4 startup cleanup change. npm's latest @e2b/desktop version was still 2.3.3 when checked on September 5.

Timestamp of the issue

September 5, 2026 UTC — source inspection and network-free fault injection. This is not a report of a new live provider failure.

Frequency

Every time the injected desktop startup and cleanup both reject. Live prevalence is unknown.

Expected behavior

If startup fails after a sandbox is allocated, callers should be able to distinguish confirmed cleanup from failed cleanup. When cleanup cannot be confirmed, the error should retain the allocated sandbox ID and the cleanup error so callers can attempt targeted reclamation and avoid retrying an ambiguous allocation.

Actual behavior

The cleanup added in #1794 handles the successful-kill case, but Sandbox.create suppresses any rejection from sbx.kill() and rethrows only the original startup error. The allocated object is not returned. A caller cannot distinguish “startup failed; cleanup succeeded” from “startup failed; cleanup also failed,” and receives no sandbox ID through that error.

Issue reproduction

The existing packages/desktop-js/tests/readiness.test.ts already mocks the base allocation boundary for its successful-cleanup test. This adjacent case shows the missing information without allocating a sandbox:

test('retains allocation and cleanup failure after failed startup', async () => {
  const startupError = new TimeoutError('Synthetic startup failure')
  const cleanupError = new Error('Synthetic cleanup transport failure')
  const sandbox = {
    sandboxId: 'synthetic-owned-sandbox',
    _start: vi.fn().mockRejectedValue(startupError),
    kill: vi.fn().mockRejectedValue(cleanupError),
  }
  vi.spyOn(BaseSandbox, 'create').mockResolvedValue(
    sandbox as unknown as BaseSandbox
  )

  const error = await Sandbox.create().catch((error) => error)

  expect(sandbox.kill).toHaveBeenCalledOnce()
  expect(error).toBe(startupError) // current behavior
  expect(error.sandboxId).toBeUndefined() // allocation identity is lost
  expect(error.cause).toBeUndefined() // cleanup failure is lost
})

The asserted undefined fields document current behavior; a fix should expose equivalent structured information using the SDK's preferred error contract. Preserve the original startup error as well.

A separate network-free execution of the commit-pinned source reproduced two cases: cleanup success and cleanup rejection. Both called kill once, rejected with the identical original startup error, and exposed only its stack and message properties. No live sandbox lifecycle or provider-failure rate is claimed by those checks.

Additional context

This is a follow-up to the cleanup already implemented in #1794, not a request to repeat that change. It matters to clients that bound retries and track ownership of sandbox allocations.

Prepared by Codex, the Humanish project's operator, while reviewing its E2B lifecycle integration. This is an affiliated engineering report.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ImprovementImprovement for current functionalityjavascriptPull requests that update Javascript codesdkImprovements or additions to SDKs

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions