From 16390cfd7610e092b5d2caf28976175f2dd7470c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E7=B4=AB=E8=BE=B0?= <155808914+hxaxd@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:37:23 +0800 Subject: [PATCH 1/3] fix(sdk): clean up sandbox when MCP gateway startup fails (#1547) ## Problem Fixes #1498. `Sandbox.create` allocates a remote sandbox before starting `mcp-gateway`. If gateway startup returns a non-zero exit code or throws an exception, creation fails before the sandbox object is returned. As a result, the caller has no sandbox ID to clean up, and the orphaned sandbox continues consuming resources until it times out. This state transition exists in synchronous Python, asynchronous Python, and JavaScript/TypeScript. ## Changes - Add a rollback boundary around MCP gateway startup in all three SDK implementations. - Attempt to kill the newly allocated sandbox when gateway startup fails. - Keep cleanup best-effort so a failed or cancelled `kill` does not replace the original gateway error. - Add real integration coverage for synchronous Python, asynchronous Python, and TypeScript. - Add a patch changeset for `e2b` and `@e2b/python-sdk`. ## Usage Behavior No API changes are required. Existing code continues to receive the original startup error, but a failed creation will normally no longer leave a sandbox behind. ## Validation Integration tests have been added and verified successfully. ## Notes PR #1499 is a friendly competing implementation for the same issue. The maintainer requested that its mocked tests be replaced with real integration tests, but the PR received no further updates for several days. The maintainer subsequently confirmed that an alternative contribution could be submitted as long as it was implemented independently from scratch, so this PR was prepared only after that confirmation. --- .changeset/quiet-sandboxes-clean.md | 6 ++++ packages/js-sdk/src/sandbox/index.ts | 25 +++++++------ packages/js-sdk/tests/sandbox/create.test.ts | 35 ++++++++++++++++++- packages/python-sdk/e2b/sandbox_async/main.py | 21 +++++++---- packages/python-sdk/e2b/sandbox_sync/main.py | 21 +++++++---- .../tests/async/sandbox_async/test_create.py | 26 ++++++++++++++ .../tests/sync/sandbox_sync/test_create.py | 26 ++++++++++++++ 7 files changed, 135 insertions(+), 25 deletions(-) create mode 100644 .changeset/quiet-sandboxes-clean.md diff --git a/.changeset/quiet-sandboxes-clean.md b/.changeset/quiet-sandboxes-clean.md new file mode 100644 index 0000000000..83334e7eb4 --- /dev/null +++ b/.changeset/quiet-sandboxes-clean.md @@ -0,0 +1,6 @@ +--- +"e2b": patch +"@e2b/python-sdk": patch +--- + +Kill newly created sandboxes when MCP gateway startup fails. diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 56caea6300..6f79576b2b 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -320,17 +320,22 @@ export class Sandbox extends SandboxApi { if (sandboxOpts?.mcp) { sandbox.mcpToken = crypto.randomUUID() - const res = await sandbox.commands.run( - `mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`, - { - user: 'root', - envs: { - GATEWAY_ACCESS_TOKEN: sandbox.mcpToken ?? '', - }, + try { + const res = await sandbox.commands.run( + `mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`, + { + user: 'root', + envs: { + GATEWAY_ACCESS_TOKEN: sandbox.mcpToken ?? '', + }, + } + ) + if (res.exitCode !== 0) { + throw new Error(`Failed to start MCP gateway: ${res.stderr}`) } - ) - if (res.exitCode !== 0) { - throw new Error(`Failed to start MCP gateway: ${res.stderr}`) + } catch (error) { + await sandbox.kill().catch(() => undefined) + throw error } } diff --git a/packages/js-sdk/tests/sandbox/create.test.ts b/packages/js-sdk/tests/sandbox/create.test.ts index 69251c8a5a..87fa079b4b 100644 --- a/packages/js-sdk/tests/sandbox/create.test.ts +++ b/packages/js-sdk/tests/sandbox/create.test.ts @@ -1,4 +1,4 @@ -import { assert, test } from 'vitest' +import { assert, expect, test } from 'vitest' import { Sandbox } from '../../src' import { template, isDebug } from '../setup.js' @@ -32,3 +32,36 @@ test.skipIf(isDebug)('metadata', async () => { await sbx.kill() } }) + +test.skipIf(isDebug)( + 'MCP gateway start failure kills the created sandbox', + async () => { + const metadata = { mcpGatewayCleanupTestId: crypto.randomUUID() } + const query = { state: ['running' as const], metadata } + let remainingSandboxes: Awaited< + ReturnType['nextItems']> + > = [] + + try { + await expect( + Sandbox.create({ + timeoutMs: 60_000, + metadata, + mcp: { invalid_server: {} } as never, + }) + ).rejects.toThrow('Failed to start MCP gateway') + + remainingSandboxes = await Sandbox.list({ query }).nextItems() + expect(remainingSandboxes).toEqual([]) + } finally { + remainingSandboxes = await Sandbox.list({ query }) + .nextItems() + .catch(() => remainingSandboxes) + await Promise.all( + remainingSandboxes.map((sandbox) => + Sandbox.kill(sandbox.sandboxId).catch(() => false) + ) + ) + } + } +) diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index 791b9b360d..ccc90e4b75 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -242,13 +242,20 @@ async def create( token = str(uuid.uuid4()) sandbox._mcp_token = token - res = await sandbox.commands.run( - f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", - user="root", - envs={"GATEWAY_ACCESS_TOKEN": token}, - ) - if res.exit_code != 0: - raise Exception(f"Failed to start MCP gateway: {res.stderr}") + try: + res = await sandbox.commands.run( + f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", + user="root", + envs={"GATEWAY_ACCESS_TOKEN": token}, + ) + if res.exit_code != 0: + raise Exception(f"Failed to start MCP gateway: {res.stderr}") + except BaseException: + try: + await sandbox.kill() + except BaseException: + pass + raise return sandbox diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index df73f2b086..36ee20fa85 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -228,13 +228,20 @@ def create( token = str(uuid.uuid4()) sandbox._mcp_token = token - res = sandbox.commands.run( - f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", - user="root", - envs={"GATEWAY_ACCESS_TOKEN": token}, - ) - if res.exit_code != 0: - raise Exception(f"Failed to start MCP gateway: {res.stderr}") + try: + res = sandbox.commands.run( + f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", + user="root", + envs={"GATEWAY_ACCESS_TOKEN": token}, + ) + if res.exit_code != 0: + raise Exception(f"Failed to start MCP gateway: {res.stderr}") + except BaseException: + try: + sandbox.kill() + except BaseException: + pass + raise return sandbox diff --git a/packages/python-sdk/tests/async/sandbox_async/test_create.py b/packages/python-sdk/tests/async/sandbox_async/test_create.py index aa17c2c52f..6c922f7cb0 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_create.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_create.py @@ -1,5 +1,6 @@ import asyncio from typing import Any, cast +from uuid import uuid4 import httpx import pytest @@ -36,6 +37,31 @@ async def test_metadata(async_sandbox_factory): assert False, "Sandbox not found" +@pytest.mark.skip_debug() +async def test_mcp_gateway_start_failure_kills_created_sandbox(): + metadata = {"mcp_gateway_cleanup_test_id": str(uuid4())} + query = SandboxQuery(state=[SandboxState.RUNNING], metadata=metadata) + remaining_sandboxes = [] + + try: + with pytest.raises(Exception, match="Failed to start MCP gateway"): + await AsyncSandbox.create( + timeout=60, + metadata=metadata, + mcp=cast(Any, {"invalid_server": {}}), + ) + + remaining_sandboxes = await AsyncSandbox.list(query=query).next_items() + assert remaining_sandboxes == [] + finally: + try: + remaining_sandboxes = await AsyncSandbox.list(query=query).next_items() + except Exception: + pass + for sandbox in remaining_sandboxes: + await AsyncSandbox.kill(sandbox.sandbox_id) + + def test_create_payload_serializes_auto_resume_enabled(): body = NewSandbox( template_id="template-id", diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_create.py b/packages/python-sdk/tests/sync/sandbox_sync/test_create.py index 737e346d1a..86c945455a 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_create.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_create.py @@ -1,5 +1,6 @@ from time import sleep from typing import Any, cast +from uuid import uuid4 import httpx import pytest @@ -37,6 +38,31 @@ def test_metadata(sandbox_factory): assert False, "Sandbox not found" +@pytest.mark.skip_debug() +def test_mcp_gateway_start_failure_kills_created_sandbox(): + metadata = {"mcp_gateway_cleanup_test_id": str(uuid4())} + query = SandboxQuery(state=[SandboxState.RUNNING], metadata=metadata) + remaining_sandboxes = [] + + try: + with pytest.raises(Exception, match="Failed to start MCP gateway"): + Sandbox.create( + timeout=60, + metadata=metadata, + mcp=cast(Any, {"invalid_server": {}}), + ) + + remaining_sandboxes = Sandbox.list(query=query).next_items() + assert remaining_sandboxes == [] + finally: + try: + remaining_sandboxes = Sandbox.list(query=query).next_items() + except Exception: + pass + for sandbox in remaining_sandboxes: + Sandbox.kill(sandbox.sandbox_id) + + def test_create_payload_serializes_auto_resume_enabled(): body = NewSandbox( template_id="template-id", From 89c902b3c5363305f0d015b0da896ebbb20e3960 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Mon, 13 Jul 2026 18:55:30 +0200 Subject: [PATCH 2/3] test(sdk): force a real MCP gateway startup failure in cleanup tests The tests relied on `mcp: {invalid_server: {}}` making mcp-gateway exit non-zero, but the gateway accepts unknown server names and starts fine, so Sandbox.create succeeded and the expected rejection never happened. Pin the tests to the base template, which has no mcp-gateway binary, so gateway startup genuinely fails after the sandbox is allocated. The failure now surfaces as CommandExitError/CommandExitException rather than the exit-code message, so match on the exception type instead. Co-Authored-By: Claude Fable 5 --- packages/js-sdk/tests/sandbox/create.test.ts | 8 +++++--- .../python-sdk/tests/async/sandbox_async/test_create.py | 9 ++++++--- .../python-sdk/tests/sync/sandbox_sync/test_create.py | 9 ++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/js-sdk/tests/sandbox/create.test.ts b/packages/js-sdk/tests/sandbox/create.test.ts index 87fa079b4b..d98c7d616b 100644 --- a/packages/js-sdk/tests/sandbox/create.test.ts +++ b/packages/js-sdk/tests/sandbox/create.test.ts @@ -1,6 +1,6 @@ import { assert, expect, test } from 'vitest' -import { Sandbox } from '../../src' +import { CommandExitError, Sandbox } from '../../src' import { template, isDebug } from '../setup.js' test.skipIf(isDebug)('create', async () => { @@ -43,13 +43,15 @@ test.skipIf(isDebug)( > = [] try { + // The base template has no mcp-gateway binary, so gateway startup + // reliably fails after the sandbox has been allocated. await expect( - Sandbox.create({ + Sandbox.create(template, { timeoutMs: 60_000, metadata, mcp: { invalid_server: {} } as never, }) - ).rejects.toThrow('Failed to start MCP gateway') + ).rejects.toThrow(CommandExitError) remainingSandboxes = await Sandbox.list({ query }).nextItems() expect(remainingSandboxes).toEqual([]) diff --git a/packages/python-sdk/tests/async/sandbox_async/test_create.py b/packages/python-sdk/tests/async/sandbox_async/test_create.py index 6c922f7cb0..50e94aa1e3 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_create.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_create.py @@ -5,7 +5,7 @@ import httpx import pytest -from e2b import AsyncSandbox, SandboxQuery, SandboxState +from e2b import AsyncSandbox, CommandExitException, SandboxQuery, SandboxState from e2b.api.client.models import ( NewSandbox, SandboxAutoResumeConfig, @@ -38,14 +38,17 @@ async def test_metadata(async_sandbox_factory): @pytest.mark.skip_debug() -async def test_mcp_gateway_start_failure_kills_created_sandbox(): +async def test_mcp_gateway_start_failure_kills_created_sandbox(template): metadata = {"mcp_gateway_cleanup_test_id": str(uuid4())} query = SandboxQuery(state=[SandboxState.RUNNING], metadata=metadata) remaining_sandboxes = [] try: - with pytest.raises(Exception, match="Failed to start MCP gateway"): + # The base template has no mcp-gateway binary, so gateway startup + # reliably fails after the sandbox has been allocated. + with pytest.raises(CommandExitException): await AsyncSandbox.create( + template, timeout=60, metadata=metadata, mcp=cast(Any, {"invalid_server": {}}), diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_create.py b/packages/python-sdk/tests/sync/sandbox_sync/test_create.py index 86c945455a..aeb0776c14 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_create.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_create.py @@ -5,7 +5,7 @@ import httpx import pytest -from e2b import Sandbox, SandboxState +from e2b import CommandExitException, Sandbox, SandboxState from e2b.api.client.models import ( NewSandbox, SandboxAutoResumeConfig, @@ -39,14 +39,17 @@ def test_metadata(sandbox_factory): @pytest.mark.skip_debug() -def test_mcp_gateway_start_failure_kills_created_sandbox(): +def test_mcp_gateway_start_failure_kills_created_sandbox(template): metadata = {"mcp_gateway_cleanup_test_id": str(uuid4())} query = SandboxQuery(state=[SandboxState.RUNNING], metadata=metadata) remaining_sandboxes = [] try: - with pytest.raises(Exception, match="Failed to start MCP gateway"): + # The base template has no mcp-gateway binary, so gateway startup + # reliably fails after the sandbox has been allocated. + with pytest.raises(CommandExitException): Sandbox.create( + template, timeout=60, metadata=metadata, mcp=cast(Any, {"invalid_server": {}}), From 987aeae80cde777a44d5c67e3af16b1c72d1e06b Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:11:12 +0200 Subject: [PATCH 3/3] fix(sdk): surface MCP gateway startup failure with a clear error and preserve cancellation Address review findings on the rollback block in all three SDKs: - The manual exit-code check was dead code: foreground commands.run already throws CommandExitError/CommandExitException on non-zero exit, so the documented "Failed to start MCP gateway" message was never produced. Catch the command exit error and re-raise it as SandboxError/SandboxException with that message and the stderr. - In async Python, re-raise asyncio.CancelledError from the best-effort kill() so caller cancellation (e.g. asyncio.timeout) is honored instead of being swallowed; suppress only ordinary cleanup failures in the sync path too. - Tests assert the wrapped message again, which is now genuinely reachable. Co-Authored-By: Claude Fable 5 --- .changeset/quiet-sandboxes-clean.md | 2 +- packages/js-sdk/src/sandbox/index.ts | 11 ++++++----- packages/js-sdk/tests/sandbox/create.test.ts | 4 ++-- packages/python-sdk/e2b/sandbox_async/main.py | 17 ++++++++++++----- packages/python-sdk/e2b/sandbox_sync/main.py | 14 +++++++++----- .../tests/async/sandbox_async/test_create.py | 4 ++-- .../tests/sync/sandbox_sync/test_create.py | 4 ++-- 7 files changed, 34 insertions(+), 22 deletions(-) diff --git a/.changeset/quiet-sandboxes-clean.md b/.changeset/quiet-sandboxes-clean.md index 83334e7eb4..f92bfb3eac 100644 --- a/.changeset/quiet-sandboxes-clean.md +++ b/.changeset/quiet-sandboxes-clean.md @@ -3,4 +3,4 @@ "@e2b/python-sdk": patch --- -Kill newly created sandboxes when MCP gateway startup fails. +Kill newly created sandboxes when MCP gateway startup fails. The failure now surfaces as `SandboxError` (JS) / `SandboxException` (Python) with a `Failed to start MCP gateway: ` message instead of a bare command exit error. diff --git a/packages/js-sdk/src/sandbox/index.ts b/packages/js-sdk/src/sandbox/index.ts index 6f79576b2b..baa9021168 100644 --- a/packages/js-sdk/src/sandbox/index.ts +++ b/packages/js-sdk/src/sandbox/index.ts @@ -11,6 +11,7 @@ import { EnvdApiClient, handleEnvdApiError } from '../envd/api' import { createEnvdFetch, createEnvdRpcFetch } from '../envd/http2' import { createRpcLogger } from '../logs' import { Commands, Pty } from './commands' +import { CommandExitError } from './commands/commandHandle' import { Filesystem } from './filesystem' import { Git } from './git' import { @@ -29,7 +30,7 @@ import { } from './sandboxApi' import { getSignature } from './signature' import { compareVersions } from 'compare-versions' -import { InvalidArgumentError, TemplateError } from '../errors' +import { InvalidArgumentError, SandboxError, TemplateError } from '../errors' import { ENVD_DEBUG_FALLBACK, ENVD_DEFAULT_USER } from '../envd/versions' import { shellQuote } from '../utils' @@ -321,7 +322,7 @@ export class Sandbox extends SandboxApi { if (sandboxOpts?.mcp) { sandbox.mcpToken = crypto.randomUUID() try { - const res = await sandbox.commands.run( + await sandbox.commands.run( `mcp-gateway --config ${shellQuote(JSON.stringify(sandboxOpts.mcp))}`, { user: 'root', @@ -330,11 +331,11 @@ export class Sandbox extends SandboxApi { }, } ) - if (res.exitCode !== 0) { - throw new Error(`Failed to start MCP gateway: ${res.stderr}`) - } } catch (error) { await sandbox.kill().catch(() => undefined) + if (error instanceof CommandExitError) { + throw new SandboxError(`Failed to start MCP gateway: ${error.stderr}`) + } throw error } } diff --git a/packages/js-sdk/tests/sandbox/create.test.ts b/packages/js-sdk/tests/sandbox/create.test.ts index d98c7d616b..20d491aed3 100644 --- a/packages/js-sdk/tests/sandbox/create.test.ts +++ b/packages/js-sdk/tests/sandbox/create.test.ts @@ -1,6 +1,6 @@ import { assert, expect, test } from 'vitest' -import { CommandExitError, Sandbox } from '../../src' +import { Sandbox } from '../../src' import { template, isDebug } from '../setup.js' test.skipIf(isDebug)('create', async () => { @@ -51,7 +51,7 @@ test.skipIf(isDebug)( metadata, mcp: { invalid_server: {} } as never, }) - ).rejects.toThrow(CommandExitError) + ).rejects.toThrow('Failed to start MCP gateway') remainingSandboxes = await Sandbox.list({ query }).nextItems() expect(remainingSandboxes).toEqual([]) diff --git a/packages/python-sdk/e2b/sandbox_async/main.py b/packages/python-sdk/e2b/sandbox_async/main.py index ccc90e4b75..c8b537f350 100644 --- a/packages/python-sdk/e2b/sandbox_async/main.py +++ b/packages/python-sdk/e2b/sandbox_async/main.py @@ -1,3 +1,4 @@ +import asyncio import datetime import json import logging @@ -16,9 +17,11 @@ from e2b.envd.api import ENVD_API_HEALTH_ROUTE, ahandle_envd_api_exception from e2b.envd.versions import ENVD_DEBUG_FALLBACK from e2b.exceptions import ( + SandboxException, TemplateException, format_request_timeout_error, ) +from e2b.sandbox.commands.command_handle import CommandExitException from e2b.sandbox.main import SandboxOpts from e2b.sandbox.sandbox_api import ( McpServer, @@ -243,18 +246,22 @@ async def create( sandbox._mcp_token = token try: - res = await sandbox.commands.run( + await sandbox.commands.run( f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", user="root", envs={"GATEWAY_ACCESS_TOKEN": token}, ) - if res.exit_code != 0: - raise Exception(f"Failed to start MCP gateway: {res.stderr}") - except BaseException: + except BaseException as e: try: await sandbox.kill() - except BaseException: + except asyncio.CancelledError: + raise + except Exception: pass + if isinstance(e, CommandExitException): + raise SandboxException( + f"Failed to start MCP gateway: {e.stderr}" + ) from e raise return sandbox diff --git a/packages/python-sdk/e2b/sandbox_sync/main.py b/packages/python-sdk/e2b/sandbox_sync/main.py index 36ee20fa85..8530457c59 100644 --- a/packages/python-sdk/e2b/sandbox_sync/main.py +++ b/packages/python-sdk/e2b/sandbox_sync/main.py @@ -14,9 +14,11 @@ from e2b.envd.api import ENVD_API_HEALTH_ROUTE, handle_envd_api_exception from e2b.envd.versions import ENVD_DEBUG_FALLBACK from e2b.exceptions import ( + SandboxException, TemplateException, format_request_timeout_error, ) +from e2b.sandbox.commands.command_handle import CommandExitException from e2b.sandbox.main import SandboxOpts from e2b.sandbox.sandbox_api import ( McpServer, @@ -229,18 +231,20 @@ def create( sandbox._mcp_token = token try: - res = sandbox.commands.run( + sandbox.commands.run( f"mcp-gateway --config {shlex.quote(json.dumps(mcp))}", user="root", envs={"GATEWAY_ACCESS_TOKEN": token}, ) - if res.exit_code != 0: - raise Exception(f"Failed to start MCP gateway: {res.stderr}") - except BaseException: + except BaseException as e: try: sandbox.kill() - except BaseException: + except Exception: pass + if isinstance(e, CommandExitException): + raise SandboxException( + f"Failed to start MCP gateway: {e.stderr}" + ) from e raise return sandbox diff --git a/packages/python-sdk/tests/async/sandbox_async/test_create.py b/packages/python-sdk/tests/async/sandbox_async/test_create.py index 50e94aa1e3..9082adac36 100644 --- a/packages/python-sdk/tests/async/sandbox_async/test_create.py +++ b/packages/python-sdk/tests/async/sandbox_async/test_create.py @@ -5,7 +5,7 @@ import httpx import pytest -from e2b import AsyncSandbox, CommandExitException, SandboxQuery, SandboxState +from e2b import AsyncSandbox, SandboxException, SandboxQuery, SandboxState from e2b.api.client.models import ( NewSandbox, SandboxAutoResumeConfig, @@ -46,7 +46,7 @@ async def test_mcp_gateway_start_failure_kills_created_sandbox(template): try: # The base template has no mcp-gateway binary, so gateway startup # reliably fails after the sandbox has been allocated. - with pytest.raises(CommandExitException): + with pytest.raises(SandboxException, match="Failed to start MCP gateway"): await AsyncSandbox.create( template, timeout=60, diff --git a/packages/python-sdk/tests/sync/sandbox_sync/test_create.py b/packages/python-sdk/tests/sync/sandbox_sync/test_create.py index aeb0776c14..03412141fe 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/test_create.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/test_create.py @@ -5,7 +5,7 @@ import httpx import pytest -from e2b import CommandExitException, Sandbox, SandboxState +from e2b import Sandbox, SandboxException, SandboxState from e2b.api.client.models import ( NewSandbox, SandboxAutoResumeConfig, @@ -47,7 +47,7 @@ def test_mcp_gateway_start_failure_kills_created_sandbox(template): try: # The base template has no mcp-gateway binary, so gateway startup # reliably fails after the sandbox has been allocated. - with pytest.raises(CommandExitException): + with pytest.raises(SandboxException, match="Failed to start MCP gateway"): Sandbox.create( template, timeout=60,