feat(tools): batch_view_files / batch_terminal_execute — sandbox-side concurrency#791
feat(tools): batch_view_files / batch_terminal_execute — sandbox-side concurrency#791seanturner83 wants to merge 2 commits into
Conversation
… concurrency
make_model_settings sets parallel_tool_calls=False (one tool call per turn), so
an agent mapping a codebase pays a full model round-trip per file read / command.
These two function tools let it fan out in a SINGLE call: the reads/execs run
concurrently inside the sandbox (asyncio.gather, semaphore-bounded to 8) and the
combined results come back in one tool response — a real wall-clock win on the
recon-heavy phase of a scan.
- batch_view_files(paths): concurrent read of up to 50 files; per-file result
{path, content|error, truncated}, order preserved (results[i] ↔ paths[i]).
- batch_terminal_execute(commands): concurrent run of up to 50 INDEPENDENT shell
commands; per-command {command, exit_code, stdout, stderr}.
Both are session-injected at the core (_view_files_impl / _exec_impl) with a thin
@function_tool shell, so the concurrency + isolation logic is directly testable
without the SDK tool-call layer. Per-item error isolation: one failing read/exec
returns its own error entry, never aborts the batch. Concurrency is bounded so a
large batch can't swamp the sandbox; batch size capped at 50.
Registered in _BASE_TOOLS so root + child agents get them. Session accessed via
the same ctx.context["sandbox_session"] convention the existing tools use. Tools
self-describe; no prompt change — models reach for them unprompted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds concurrent sandbox tools for batched file reads and shell commands. The main changes are:
Confidence Score: 4/5Workspace traversal and valid batches exceeding their tool deadlines need to be fixed before merging.
strix/tools/batch/tools.py
|
| Filename | Overview |
|---|---|
| strix/tools/batch/tools.py | Adds bounded concurrent file and command execution, but path handling permits workspace traversal and maximum batches can outlast the enclosing tool deadlines. |
| strix/agents/factory.py | Imports and registers the two new tools using the existing base-tool pattern. |
| tests/tools/test_batch_tools.py | Covers ordering, overlap, item limits, missing sessions, and basic per-item failures with a fake session. |
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
strix/tools/batch/tools.py:58-59
**Workspace Prefix Allows Traversal**
A path such as `workspace/../../etc/passwd` is passed to `cat` as `/workspace/../../etc/passwd`, so it resolves outside the documented workspace root. Callers can read container files instead of receiving only repository content.
### Issue 2 of 2
strix/tools/batch/tools.py:29
**Valid Batches Exceed Tool Deadline**
With 50 items, eight workers, and a 60-second timeout per item, seven waves can take about 420 seconds. Slow but valid file batches exceed the 180-second tool timeout, and command batches exceed the 300-second timeout, so the whole call can be cancelled before returning isolated results.
Reviews (1): Last reviewed commit: "feat(tools): batch_view_files / batch_te..." | Re-trigger Greptile
| target = rel if rel.startswith("workspace/") else f"workspace/{rel}" | ||
| result = await session.exec("cat", f"/{target}", timeout=_DEFAULT_TIMEOUT) |
There was a problem hiding this comment.
Workspace Prefix Allows Traversal
A path such as workspace/../../etc/passwd is passed to cat as /workspace/../../etc/passwd, so it resolves outside the documented workspace root. Callers can read container files instead of receiving only repository content.
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/tools/batch/tools.py
Line: 58-59
Comment:
**Workspace Prefix Allows Traversal**
A path such as `workspace/../../etc/passwd` is passed to `cat` as `/workspace/../../etc/passwd`, so it resolves outside the documented workspace root. Callers can read container files instead of receiving only repository content.
How can I resolve this? If you propose a fix, please make it concise.| _MAX_CONCURRENCY = 8 # bounded fan-out — don't swamp the sandbox | ||
| _MAX_ITEMS = 50 # cap batch size (a 200-file batch is a smell) | ||
| _DEFAULT_TIMEOUT = 60 | ||
|
|
There was a problem hiding this comment.
Valid Batches Exceed Tool Deadline
With 50 items, eight workers, and a 60-second timeout per item, seven waves can take about 420 seconds. Slow but valid file batches exceed the 180-second tool timeout, and command batches exceed the 300-second timeout, so the whole call can be cancelled before returning isolated results.
Prompt To Fix With AI
This is a comment left during a code review.
Path: strix/tools/batch/tools.py
Line: 29
Comment:
**Valid Batches Exceed Tool Deadline**
With 50 items, eight workers, and a 60-second timeout per item, seven waves can take about 420 seconds. Slow but valid file batches exceed the 180-second tool timeout, and command batches exceed the 300-second timeout, so the whole call can be cancelled before returning isolated results.
How can I resolve this? If you propose a fix, please make it concise.Two Greptile findings on usestrix#791: - P1 security: batch_view_files prefixed `workspace/` but didn't guard `..`, so `workspace/../../etc/passwd` was cat'd verbatim as `/workspace/../../etc/passwd` and read container files outside the target. Added `_safe_workspace_path()`: normalizes the path and rejects anything that resolves outside `/workspace` (per-item error, never handed to cat); the safe entries in the batch still read. - P1: a full valid batch could exceed the enclosing @function_tool deadline and be cancelled before returning isolated results. Worst case is ceil(_MAX_ITEMS/_MAX_CONCURRENCY)=7 waves of the per-item timeout; the old 60s per item => ~420s > the 180s/300s tool deadlines. Lowered per-item timeouts (view 20s, exec 40s) so 7 waves stay under the tool deadline, and named all four constants so the invariant is explicit (+ a test asserting it holds). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — both P1s were real, fixed in 659f643:
|
What
Two new sandbox tools that let the agent fan out file reads / shell commands in a single tool call, running them concurrently inside the sandbox:
batch_view_files(paths)— concurrent read of up to 50 files; per-file result{path, content|error, truncated}, order preserved (results[i]↔paths[i]).batch_terminal_execute(commands)— concurrent run of up to 50 independent shell commands; per-command{command, exit_code, stdout, stderr}.Why
make_model_settingssetsparallel_tool_calls=False(one tool call per turn). So an agent mapping a codebase — reading 20 files, running 10 independent greps/probes — pays a full model round-trip per item. On the recon-heavy phase of a scan that's the dominant wall-clock cost.These tools collapse that to one round-trip: the reads/execs run concurrently in the sandbox (
asyncio.gather, semaphore-bounded to 8), combined results returned in one response.In practice the models reach for them unprompted — across real multi-repo scans, both were invoked heavily by the agent with no prompt guidance (they self-describe via their docstrings), so no system-prompt change is included.
Design
_view_files_impl/_exec_impl) with a thin@function_toolshell, so the concurrency + isolation logic is directly unit-testable without the SDK tool-call layer.ctx.context["sandbox_session"]convention the existing tools use; registered in_BASE_TOOLSso root + child agents get them.Tests
tests/tools/test_batch_tools.py(9, fake-session): order preservation, per-item error isolation, observable concurrency (bounded overlap), batch-size cap, missing-session guard, empty-input.ruffclean.🤖 Generated with Claude Code