fix(sandbox): forward bash command timeout to remote providers (#6782)

This commit is contained in:
OUAO-FRANK 2026-10-14 06:47:38 +08:00 committed by GitHub
parent 8b62537847
commit 12397d2142
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 13 additions and 10 deletions

View File

@ -327,10 +327,10 @@ class SandboxConfig(BaseModel):
"(1.9.3+, recommended 1.11.0) enforce it server-side through "
"`hard_timeout`; the frozen legacy `all-in-one-sandbox:latest` image "
"only gets the bounded host-side request. `bash_command_timeout` is "
"used by providers that explicitly wire this setting (currently "
"LocalSandbox, AioSandbox, and OpenSandbox). Other providers retain "
"their provider-specific command defaults unless a caller supplies an "
"explicit timeout."
"forwarded to each provider's command API. LocalSandbox, AioSandbox, "
"OpenSandbox, E2B, BoxLite, and Tenki apply it to bash commands; "
"providers without per-command timeout support retain their own "
"defaults."
),
)

View File

@ -116,7 +116,7 @@ subshell. Keep parser filtering as a backstop; test ignored roots, metacharacter
symlinks, and visible depth/size bounds.
- Every sandbox tool keeps a model-visible `description` field for a human-readable progress label, but the field is optional and defaults to an empty string. Tool execution must depend only on its operational arguments; the frontend supplies localized fallback labels when a provider omits `description`.
- `bash` - Execute commands with path translation and error handling. For `LocalSandbox`, POSIX/Windows output uses bounded pipe-drain threads with `/dev/null` stdin; Windows decodes locale-code-page/UTF-8/CRLF/bare-CR output with universal-newline translation, while POSIX stays byte-decoded. POSIX background commands return without blocking on inherited pipes; unredirected output is drained without unbounded temp files. Commands that read stdin get immediate EOF. `bash_command_timeout` sets T (600s) for Local/AIO/OpenSandbox; others keep defaults unless explicit. Local: T is a wall-clock process-group deadline. Supported-semver AIO: server-side `hard_timeout`; frozen legacy `all-in-one-sandbox:latest`: command requests get a bounded host `T+5s` wait (`max_retries=0`), so a wedged command request cannot hold the sandbox lock for the SDK's full 600s budget; file/list RPCs are not bounded by it. Explicit AIO shell/bash session creation uses a separate 5s/no-retry host bound; an ambiguous create is never replayed and forces container recycle before warm reuse. Never replay ambiguous outcomes: transport timeout, terminated, no_change_timeout, unknown statuses. `hard_timeout` => terminated + Exit Code: 124, keep session; `no_change_timeout` => may still be running, fence session generation. The description scopes host-environment probes to LocalSandbox (`uname -s`, then `sw_vers` on Darwin; Linux files only when policy permits), gives conditional recovery for local path/`file://` rejections, and tells the model to background long-lived processes. See `LocalSandbox.execute_command`, its platform runners, and `bash_tool`'s docstring.
- `bash` - Execute commands with path translation and error handling. For `LocalSandbox`, POSIX/Windows output uses bounded pipe-drain threads with `/dev/null` stdin; Windows decodes locale-code-page/UTF-8/CRLF/bare-CR output with universal-newline translation, while POSIX stays byte-decoded. POSIX background commands return without blocking on inherited pipes; unredirected output is drained without unbounded temp files. Commands that read stdin get immediate EOF. `bash_command_timeout` is forwarded to providers with a per-command timeout API (Local/AIO/OpenSandbox/E2B/BoxLite/Tenki); providers without one keep their own defaults. Local: T is a wall-clock process-group deadline. Supported-semver AIO: server-side `hard_timeout`; frozen legacy `all-in-one-sandbox:latest`: command requests get a bounded host `T+5s` wait (`max_retries=0`), so a wedged command request cannot hold the sandbox lock for the SDK's full 600s budget; file/list RPCs are not bounded by it. Explicit AIO shell/bash session creation uses a separate 5s/no-retry host bound; an ambiguous create is never replayed and forces container recycle before warm reuse. Never replay ambiguous outcomes: transport timeout, terminated, no_change_timeout, unknown statuses. `hard_timeout` => terminated + Exit Code: 124, keep session; `no_change_timeout` => may still be running, fence session generation. The description scopes host-environment probes to LocalSandbox (`uname -s`, then `sw_vers` on Darwin; Linux files only when policy permits), gives conditional recovery for local path/`file://` rejections, and tells the model to background long-lived processes. See `LocalSandbox.execute_command`, its platform runners, and `bash_tool`'s docstring.
- `ls` - Directory listing (tree format, max 2 levels). Remote commands precheck root existence and emit `__DF_FIND_STATUS__:missing`; `find` status 1 is always an incomplete-traversal `OSError`. `AioSandbox.list_dir` parses only `completed`/`None` on #5634's selected generation: `hard_timeout` raises `TimeoutError` and keeps that generation reusable; an ambiguous timeout or status raises `OSError`, replays nothing, and fences that generation. Missing-session 404s drop the stale id without replay. Persistent/scoped shells fence all `httpx.TransportError`s before cleanup.
- `glob` - Find files or directories below a root directory with bounded results
- `grep` - Search one text file or recursively search a directory, with optional glob filtering and bounded line-level results

View File

@ -2472,8 +2472,10 @@ def bash_tool(runtime: Runtime, command: str, description: str = "") -> str:
sandbox_cfg = get_app_config().sandbox
max_chars = sandbox_cfg.bash_output_max_chars if sandbox_cfg else 20000
command_timeout = sandbox_cfg.bash_command_timeout if sandbox_cfg else None
except Exception:
max_chars = 20000
command_timeout = None
return _truncate_bash_output(
mask_secret_values(
_execute_bash_command(
@ -2481,6 +2483,7 @@ def bash_tool(runtime: Runtime, command: str, description: str = "") -> str:
command,
runtime=runtime,
env=injected_env,
timeout=command_timeout,
),
injected_env,
),

View File

@ -1553,7 +1553,7 @@ class TestBashToolInjectsActiveSecrets:
assert captured["command"] == "echo hi"
assert captured["env"] == {"ERP_TOKEN": "tok-789", USER_ID_ENV: "u-win"}
def test_remote_bash_does_not_forward_shared_timeout(self):
def test_remote_bash_forwards_shared_timeout(self):
from deerflow.sandbox import tools as tools_mod
captured = {}
@ -1600,7 +1600,7 @@ class TestBashToolInjectsActiveSecrets:
)
assert out == "done"
assert captured["timeout"] is None
assert captured["timeout"] == 42
_SECRET = "sk-erp-9f3c-DO-NOT-LEAK"

View File

@ -1728,9 +1728,9 @@ sandbox:
# semver line (1.9.3+, recommended 1.11.0) enforce it server-side through
# `hard_timeout`; the frozen legacy `all-in-one-sandbox:latest` image only
# gets the bounded host-side request. `bash_command_timeout` is used by
# providers that explicitly wire this setting (currently LocalSandbox,
# AioSandbox, and OpenSandbox). Other providers retain their provider-specific
# command defaults unless a caller supplies an explicit timeout. For providers
# providers that expose a per-command timeout (LocalSandbox, AioSandbox,
# OpenSandbox, E2B, BoxLite, and Tenki). Providers without per-command timeout
# support retain their provider-specific command defaults. For providers
# that enforce this deadline server- or process-side, blocking foreground
# commands are terminated at the configured deadline. Long-lived processes
# should be started in the background with output redirected; unredirected