From d79fa59b28fe9b9945165e83408969fea236dd31 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:13:05 +0200 Subject: [PATCH 1/4] fix(sdk): disable timeout on background commands by default Background commands (`commands.run(..., background=True)` / `{ background: true }`) were killed after the default 60s command timeout. They now default to no timeout, while foreground commands keep the 60s default. An explicit `timeout`/`timeoutMs` still takes precedence. Co-Authored-By: Claude Opus 4.8 --- .changeset/lazy-trees-tell.md | 6 +++++ packages/js-sdk/src/sandbox/commands/index.ts | 14 ++++++++-- .../js-sdk/tests/sandbox/commands/run.test.ts | 26 +++++++++++++++++++ .../e2b/sandbox_async/commands/command.py | 13 +++++++--- .../e2b/sandbox_sync/commands/command.py | 13 +++++++--- .../async/sandbox_async/commands/test_run.py | 20 ++++++++++++++ .../sync/sandbox_sync/commands/test_run.py | 20 ++++++++++++++ 7 files changed, 102 insertions(+), 10 deletions(-) create mode 100644 .changeset/lazy-trees-tell.md diff --git a/.changeset/lazy-trees-tell.md b/.changeset/lazy-trees-tell.md new file mode 100644 index 0000000000..2a9ad911c7 --- /dev/null +++ b/.changeset/lazy-trees-tell.md @@ -0,0 +1,6 @@ +--- +'@e2b/python-sdk': patch +'e2b': patch +--- + +Background commands (`commands.run(..., background=True)` / `{ background: true }`) now default to no timeout, so they are no longer killed after the default 60s command timeout. Pass an explicit `timeout`/`timeoutMs` to set one. diff --git a/packages/js-sdk/src/sandbox/commands/index.ts b/packages/js-sdk/src/sandbox/commands/index.ts index 108a750739..cec1e3182a 100644 --- a/packages/js-sdk/src/sandbox/commands/index.ts +++ b/packages/js-sdk/src/sandbox/commands/index.ts @@ -77,7 +77,9 @@ export interface CommandStartOpts extends CommandRequestOpts { /** * Timeout for the command in **milliseconds**. * - * @default 60_000 // 60 seconds + * Background commands (`background: true`) default to no timeout. + * + * @default 60_000 // 60 seconds for foreground commands, no timeout for background commands */ timeoutMs?: number } @@ -373,6 +375,9 @@ export class Commands { * Start a new command in the background. * You can use {@link CommandHandle.wait} to wait for the command to finish and get its result. * + * Background commands default to no timeout, so they are not killed after the + * default command timeout. Pass `opts.timeoutMs` to set one explicitly. + * * @param cmd command to execute. * @param opts options for starting the command * @@ -445,7 +450,12 @@ export class Commands { [KEEPALIVE_PING_HEADER]: KEEPALIVE_PING_INTERVAL_SEC.toString(), }, signal: controller.signal, - timeoutMs: opts?.timeoutMs ?? this.defaultProcessConnectionTimeout, + // Background commands default to no timeout so they aren't killed after + // the default command timeout. An explicit `timeoutMs` still takes + // precedence. + timeoutMs: opts?.background + ? opts?.timeoutMs + : (opts?.timeoutMs ?? this.defaultProcessConnectionTimeout), } ) diff --git a/packages/js-sdk/tests/sandbox/commands/run.test.ts b/packages/js-sdk/tests/sandbox/commands/run.test.ts index e532d88bb8..1a3aee0bb4 100644 --- a/packages/js-sdk/tests/sandbox/commands/run.test.ts +++ b/packages/js-sdk/tests/sandbox/commands/run.test.ts @@ -42,3 +42,29 @@ sandboxTest('run with too short timeout', async ({ sandbox }) => { sandbox.commands.run('sleep 10', { timeoutMs: 1000 }) ).rejects.toThrow() }) + +sandboxTest( + 'background run is capped by timeout', + async ({ sandbox }) => { + const start = Date.now() + const cmd = await sandbox.commands.run('sleep 20', { + background: true, + timeoutMs: 10_000, + }) + await expect(cmd.wait()).rejects.toThrow() + // The command is capped by the timeout instead of running for the full 20s. + expect(Date.now() - start).toBeLessThan(20_000) + }, + 60_000 +) + +sandboxTest( + 'background run without timeout completes', + async ({ sandbox }) => { + // Background commands default to no timeout, so a long command completes. + const cmd = await sandbox.commands.run('sleep 20', { background: true }) + const result = await cmd.wait() + assert.equal(result.exitCode, 0) + }, + 60_000 +) diff --git a/packages/python-sdk/e2b/sandbox_async/commands/command.py b/packages/python-sdk/e2b/sandbox_async/commands/command.py index 32b75fd26b..d59c545d01 100644 --- a/packages/python-sdk/e2b/sandbox_async/commands/command.py +++ b/packages/python-sdk/e2b/sandbox_async/commands/command.py @@ -158,7 +158,7 @@ async def run( :param on_stdout: Callback for command stdout output :param on_stderr: Callback for command stderr output :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time + :param timeout: Timeout for the command in **seconds**. Using `0` will not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `CommandResult` result of the command execution @@ -176,7 +176,7 @@ async def run( on_stdout: Optional[OutputHandler[Stdout]] = None, on_stderr: Optional[OutputHandler[Stderr]] = None, stdin: Optional[bool] = None, - timeout: Optional[float] = 60, + timeout: Optional[float] = 0, request_timeout: Optional[float] = None, ) -> AsyncCommandHandle: """ @@ -190,7 +190,7 @@ async def run( :param on_stdout: Callback for command stdout output :param on_stderr: Callback for command stderr output :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time + :param timeout: Timeout for the command in **seconds**. Background commands default to `0`, which does not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `AsyncCommandHandle` handle to interact with the running command @@ -207,7 +207,7 @@ async def run( on_stdout: Optional[OutputHandler[Stdout]] = None, on_stderr: Optional[OutputHandler[Stderr]] = None, stdin: Optional[bool] = None, - timeout: Optional[float] = 60, + timeout: Optional[float] = None, request_timeout: Optional[float] = None, ): # Check version for stdin support @@ -220,6 +220,11 @@ async def run( # Default to `False` stdin = stdin or False + # When the timeout is not set, background commands default to no timeout + # (`0`) while foreground commands keep the default 60s timeout. + if timeout is None: + timeout = 0 if background else 60 + proc = await self._start( cmd, envs, diff --git a/packages/python-sdk/e2b/sandbox_sync/commands/command.py b/packages/python-sdk/e2b/sandbox_sync/commands/command.py index 512b7d9923..1fc57be042 100644 --- a/packages/python-sdk/e2b/sandbox_sync/commands/command.py +++ b/packages/python-sdk/e2b/sandbox_sync/commands/command.py @@ -157,7 +157,7 @@ def run( :param on_stdout: Callback for command stdout output :param on_stderr: Callback for command stderr output :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time + :param timeout: Timeout for the command in **seconds**. Using `0` will not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `CommandResult` result of the command execution @@ -175,7 +175,7 @@ def run( on_stdout: None = None, on_stderr: None = None, stdin: Optional[bool] = None, - timeout: Optional[float] = 60, + timeout: Optional[float] = 0, request_timeout: Optional[float] = None, ) -> CommandHandle: """ @@ -187,7 +187,7 @@ def run( :param user: User to run the command as :param cwd: Working directory to run the command :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command connection in **seconds**. Using `0` will not limit the command connection time + :param timeout: Timeout for the command in **seconds**. Background commands default to `0`, which does not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `CommandHandle` handle to interact with the running command @@ -204,7 +204,7 @@ def run( on_stdout: Optional[Callable[[str], None]] = None, on_stderr: Optional[Callable[[str], None]] = None, stdin: Optional[bool] = None, - timeout: Optional[float] = 60, + timeout: Optional[float] = None, request_timeout: Optional[float] = None, ): # Check version for stdin support @@ -217,6 +217,11 @@ def run( # Default to `False` stdin = stdin or False + # When the timeout is not set, background commands default to no timeout + # (`0`) while foreground commands keep the default 60s timeout. + if timeout is None: + timeout = 0 if background else 60 + proc = self._start( cmd, envs, diff --git a/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py b/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py index f871ebdc61..a13db3ce7e 100644 --- a/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py +++ b/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py @@ -1,3 +1,5 @@ +import time + import pytest from e2b import AsyncSandbox, TimeoutException @@ -51,3 +53,21 @@ async def test_run_with_timeout(async_sandbox: AsyncSandbox): async def test_run_with_too_short_timeout(async_sandbox: AsyncSandbox): with pytest.raises(TimeoutException): await async_sandbox.commands.run("sleep 10", timeout=2) + + +@pytest.mark.timeout(60) +async def test_background_run_is_capped_by_timeout(async_sandbox: AsyncSandbox): + start = time.time() + cmd = await async_sandbox.commands.run("sleep 20", background=True, timeout=10) + with pytest.raises(TimeoutException): + await cmd.wait() + # The command is capped by the timeout instead of running for the full 20s. + assert time.time() - start < 20 + + +@pytest.mark.timeout(60) +async def test_background_run_without_timeout_completes(async_sandbox: AsyncSandbox): + # Background commands default to no timeout, so a long command completes. + cmd = await async_sandbox.commands.run("sleep 20", background=True) + result = await cmd.wait() + assert result.exit_code == 0 diff --git a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py index e75e5ba32a..a67dea9d32 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py @@ -1,3 +1,5 @@ +import time + import pytest from e2b import Sandbox, TimeoutException @@ -56,3 +58,21 @@ def test_run_with_too_short_timeout_iterating(sandbox): with pytest.raises(TimeoutException): for _ in cmd: pass + + +@pytest.mark.timeout(60) +def test_background_run_is_capped_by_timeout(sandbox): + start = time.time() + cmd = sandbox.commands.run("sleep 20", background=True, timeout=10) + with pytest.raises(TimeoutException): + cmd.wait() + # The command is capped by the timeout instead of running for the full 20s. + assert time.time() - start < 20 + + +@pytest.mark.timeout(60) +def test_background_run_without_timeout_completes(sandbox): + # Background commands default to no timeout, so a long command completes. + cmd = sandbox.commands.run("sleep 20", background=True) + result = cmd.wait() + assert result.exit_code == 0 From 5eafd0b4543a50f582e150538652441665752f14 Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:54:28 +0200 Subject: [PATCH 2/4] test(sdk): make background no-timeout test exceed old 60s default; fix connect timeoutMs JSDoc Addresses PR review: - background no-timeout integration tests now sleep 70s (> the previous 60s default) so they actually guard the regression rather than passing under both old and new behavior. - `CommandConnectOpts` gets its own `timeoutMs` JSDoc so `connect()` tooltips no longer inherit the background-specific wording from `CommandStartOpts`. Co-Authored-By: Claude Opus 4.8 --- packages/js-sdk/src/sandbox/commands/index.ts | 16 ++++++++++++---- .../js-sdk/tests/sandbox/commands/run.test.ts | 7 ++++--- .../async/sandbox_async/commands/test_run.py | 7 ++++--- .../tests/sync/sandbox_sync/commands/test_run.py | 7 ++++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/packages/js-sdk/src/sandbox/commands/index.ts b/packages/js-sdk/src/sandbox/commands/index.ts index cec1e3182a..3870848968 100644 --- a/packages/js-sdk/src/sandbox/commands/index.ts +++ b/packages/js-sdk/src/sandbox/commands/index.ts @@ -77,9 +77,10 @@ export interface CommandStartOpts extends CommandRequestOpts { /** * Timeout for the command in **milliseconds**. * - * Background commands (`background: true`) default to no timeout. + * Foreground commands default to 60 seconds; background commands (`background: true`) + * default to no timeout. See the `run` overloads for details. * - * @default 60_000 // 60 seconds for foreground commands, no timeout for background commands + * @default 60_000 // 60 seconds */ timeoutMs?: number } @@ -89,9 +90,16 @@ export interface CommandStartOpts extends CommandRequestOpts { */ export type CommandConnectOpts = Pick< CommandStartOpts, - 'onStderr' | 'onStdout' | 'timeoutMs' + 'onStderr' | 'onStdout' > & - CommandRequestOpts + CommandRequestOpts & { + /** + * Timeout for the command in **milliseconds**. + * + * @default 60_000 // 60 seconds + */ + timeoutMs?: number + } /** * Information about a command, PTY session or start command running in the sandbox as process. diff --git a/packages/js-sdk/tests/sandbox/commands/run.test.ts b/packages/js-sdk/tests/sandbox/commands/run.test.ts index 1a3aee0bb4..fd5fa135f2 100644 --- a/packages/js-sdk/tests/sandbox/commands/run.test.ts +++ b/packages/js-sdk/tests/sandbox/commands/run.test.ts @@ -61,10 +61,11 @@ sandboxTest( sandboxTest( 'background run without timeout completes', async ({ sandbox }) => { - // Background commands default to no timeout, so a long command completes. - const cmd = await sandbox.commands.run('sleep 20', { background: true }) + // Sleep longer than the previous 60s default so this actually exercises the + // no-timeout default for background commands rather than the old 60s cap. + const cmd = await sandbox.commands.run('sleep 70', { background: true }) const result = await cmd.wait() assert.equal(result.exitCode, 0) }, - 60_000 + 120_000 ) diff --git a/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py b/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py index a13db3ce7e..b47d276c5c 100644 --- a/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py +++ b/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py @@ -65,9 +65,10 @@ async def test_background_run_is_capped_by_timeout(async_sandbox: AsyncSandbox): assert time.time() - start < 20 -@pytest.mark.timeout(60) +@pytest.mark.timeout(120) async def test_background_run_without_timeout_completes(async_sandbox: AsyncSandbox): - # Background commands default to no timeout, so a long command completes. - cmd = await async_sandbox.commands.run("sleep 20", background=True) + # Sleep longer than the previous 60s default so this actually exercises the + # no-timeout default for background commands rather than the old 60s cap. + cmd = await async_sandbox.commands.run("sleep 70", background=True) result = await cmd.wait() assert result.exit_code == 0 diff --git a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py index a67dea9d32..3adb7944a8 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py @@ -70,9 +70,10 @@ def test_background_run_is_capped_by_timeout(sandbox): assert time.time() - start < 20 -@pytest.mark.timeout(60) +@pytest.mark.timeout(120) def test_background_run_without_timeout_completes(sandbox): - # Background commands default to no timeout, so a long command completes. - cmd = sandbox.commands.run("sleep 20", background=True) + # Sleep longer than the previous 60s default so this actually exercises the + # no-timeout default for background commands rather than the old 60s cap. + cmd = sandbox.commands.run("sleep 70", background=True) result = cmd.wait() assert result.exit_code == 0 From c0c548f580c54067c089b4c06f2ebea81b1e8f5a Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:04:12 +0200 Subject: [PATCH 3/4] fix(python-sdk): treat explicit timeout=None as no timeout Distinguish an omitted `timeout` from an explicit `None` via a private sentinel: omitted resolves to the default (60s foreground, 0/no-timeout background), while an explicit `None` means no timeout (0). This restores the pre-change behavior where `run(..., timeout=None)` ran uncapped, addressing the PR review. Co-Authored-By: Claude Opus 4.8 --- .../e2b/sandbox_async/commands/command.py | 21 +++++++++++++------ .../e2b/sandbox_sync/commands/command.py | 21 +++++++++++++------ .../async/sandbox_async/commands/test_run.py | 8 +++++++ .../sync/sandbox_sync/commands/test_run.py | 8 +++++++ 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/python-sdk/e2b/sandbox_async/commands/command.py b/packages/python-sdk/e2b/sandbox_async/commands/command.py index d59c545d01..ae78b2f5e6 100644 --- a/packages/python-sdk/e2b/sandbox_async/commands/command.py +++ b/packages/python-sdk/e2b/sandbox_async/commands/command.py @@ -19,6 +19,13 @@ from e2b.sandbox_async.utils import OutputHandler +class _Unset: + """Sentinel for an omitted ``timeout`` argument (distinct from ``None``).""" + + +_UNSET = _Unset() + + class Commands: """ Module for executing commands in the sandbox. @@ -158,7 +165,7 @@ async def run( :param on_stdout: Callback for command stdout output :param on_stderr: Callback for command stderr output :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command in **seconds**. Using `0` will not limit the command run time + :param timeout: Timeout for the command in **seconds**. Using `0` or `None` will not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `CommandResult` result of the command execution @@ -190,7 +197,7 @@ async def run( :param on_stdout: Callback for command stdout output :param on_stderr: Callback for command stderr output :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command in **seconds**. Background commands default to `0`, which does not limit the command run time + :param timeout: Timeout for the command in **seconds**. Background commands default to `0`; using `0` or `None` will not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `AsyncCommandHandle` handle to interact with the running command @@ -207,7 +214,7 @@ async def run( on_stdout: Optional[OutputHandler[Stdout]] = None, on_stderr: Optional[OutputHandler[Stderr]] = None, stdin: Optional[bool] = None, - timeout: Optional[float] = None, + timeout: Union[float, None, _Unset] = _UNSET, request_timeout: Optional[float] = None, ): # Check version for stdin support @@ -220,10 +227,12 @@ async def run( # Default to `False` stdin = stdin or False - # When the timeout is not set, background commands default to no timeout - # (`0`) while foreground commands keep the default 60s timeout. - if timeout is None: + # Resolve the default timeout: foreground commands default to 60s and + # background commands to no timeout. An explicit `None` means no timeout. + if isinstance(timeout, _Unset): timeout = 0 if background else 60 + elif timeout is None: + timeout = 0 proc = await self._start( cmd, diff --git a/packages/python-sdk/e2b/sandbox_sync/commands/command.py b/packages/python-sdk/e2b/sandbox_sync/commands/command.py index 1fc57be042..9ce47067c9 100644 --- a/packages/python-sdk/e2b/sandbox_sync/commands/command.py +++ b/packages/python-sdk/e2b/sandbox_sync/commands/command.py @@ -18,6 +18,13 @@ from e2b.sandbox_sync.commands.command_handle import CommandHandle +class _Unset: + """Sentinel for an omitted ``timeout`` argument (distinct from ``None``).""" + + +_UNSET = _Unset() + + class Commands: """ Module for executing commands in the sandbox. @@ -157,7 +164,7 @@ def run( :param on_stdout: Callback for command stdout output :param on_stderr: Callback for command stderr output :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command in **seconds**. Using `0` will not limit the command run time + :param timeout: Timeout for the command in **seconds**. Using `0` or `None` will not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `CommandResult` result of the command execution @@ -187,7 +194,7 @@ def run( :param user: User to run the command as :param cwd: Working directory to run the command :param stdin: If `True`, the command will have a stdin stream that you can send data to using `sandbox.commands.send_stdin()` - :param timeout: Timeout for the command in **seconds**. Background commands default to `0`, which does not limit the command run time + :param timeout: Timeout for the command in **seconds**. Background commands default to `0`; using `0` or `None` will not limit the command run time :param request_timeout: Timeout for the request in **seconds** :return: `CommandHandle` handle to interact with the running command @@ -204,7 +211,7 @@ def run( on_stdout: Optional[Callable[[str], None]] = None, on_stderr: Optional[Callable[[str], None]] = None, stdin: Optional[bool] = None, - timeout: Optional[float] = None, + timeout: Union[float, None, _Unset] = _UNSET, request_timeout: Optional[float] = None, ): # Check version for stdin support @@ -217,10 +224,12 @@ def run( # Default to `False` stdin = stdin or False - # When the timeout is not set, background commands default to no timeout - # (`0`) while foreground commands keep the default 60s timeout. - if timeout is None: + # Resolve the default timeout: foreground commands default to 60s and + # background commands to no timeout. An explicit `None` means no timeout. + if isinstance(timeout, _Unset): timeout = 0 if background else 60 + elif timeout is None: + timeout = 0 proc = self._start( cmd, diff --git a/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py b/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py index b47d276c5c..2297eafb49 100644 --- a/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py +++ b/packages/python-sdk/tests/async/sandbox_async/commands/test_run.py @@ -72,3 +72,11 @@ async def test_background_run_without_timeout_completes(async_sandbox: AsyncSand cmd = await async_sandbox.commands.run("sleep 70", background=True) result = await cmd.wait() assert result.exit_code == 0 + + +@pytest.mark.timeout(120) +async def test_run_with_none_timeout_completes(async_sandbox: AsyncSandbox): + # `timeout=None` means no timeout, so a command longer than the 60s default + # completes instead of being capped. + cmd = await async_sandbox.commands.run("sleep 70", timeout=None) + assert cmd.exit_code == 0 diff --git a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py index 3adb7944a8..38106dd25b 100644 --- a/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py +++ b/packages/python-sdk/tests/sync/sandbox_sync/commands/test_run.py @@ -77,3 +77,11 @@ def test_background_run_without_timeout_completes(sandbox): cmd = sandbox.commands.run("sleep 70", background=True) result = cmd.wait() assert result.exit_code == 0 + + +@pytest.mark.timeout(120) +def test_run_with_none_timeout_completes(sandbox): + # `timeout=None` means no timeout, so a command longer than the 60s default + # completes instead of being capped. + cmd = sandbox.commands.run("sleep 70", timeout=None) + assert cmd.exit_code == 0 From 12850f455ad494cc6cf94e2fa7d0feae0213b65d Mon Sep 17 00:00:00 2001 From: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:10:57 +0200 Subject: [PATCH 4/4] docs(js-sdk): keep connect timeoutMs JSDoc generic instead of splitting the type Revert the `CommandConnectOpts` type split and the background note on the shared `CommandStartOpts.timeoutMs` field. The background no-timeout behavior is documented on the `run` background overload only, so `connect()`'s `timeoutMs` tooltip stays generic without any type churn. Co-Authored-By: Claude Opus 4.8 --- packages/js-sdk/src/sandbox/commands/index.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/js-sdk/src/sandbox/commands/index.ts b/packages/js-sdk/src/sandbox/commands/index.ts index 3870848968..5b08b3eae0 100644 --- a/packages/js-sdk/src/sandbox/commands/index.ts +++ b/packages/js-sdk/src/sandbox/commands/index.ts @@ -77,9 +77,6 @@ export interface CommandStartOpts extends CommandRequestOpts { /** * Timeout for the command in **milliseconds**. * - * Foreground commands default to 60 seconds; background commands (`background: true`) - * default to no timeout. See the `run` overloads for details. - * * @default 60_000 // 60 seconds */ timeoutMs?: number @@ -90,16 +87,9 @@ export interface CommandStartOpts extends CommandRequestOpts { */ export type CommandConnectOpts = Pick< CommandStartOpts, - 'onStderr' | 'onStdout' + 'onStderr' | 'onStdout' | 'timeoutMs' > & - CommandRequestOpts & { - /** - * Timeout for the command in **milliseconds**. - * - * @default 60_000 // 60 seconds - */ - timeoutMs?: number - } + CommandRequestOpts /** * Information about a command, PTY session or start command running in the sandbox as process.