Skip to content

feat(tools): batch_view_files / batch_terminal_execute — sandbox-side concurrency#791

Open
seanturner83 wants to merge 2 commits into
usestrix:mainfrom
seanturner83:feat/batch-sandbox-tools
Open

feat(tools): batch_view_files / batch_terminal_execute — sandbox-side concurrency#791
seanturner83 wants to merge 2 commits into
usestrix:mainfrom
seanturner83:feat/batch-sandbox-tools

Conversation

@seanturner83

Copy link
Copy Markdown
Contributor

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_settings sets parallel_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

  • Session-injected core (_view_files_impl / _exec_impl) with a thin @function_tool shell, so the concurrency + isolation logic is directly unit-testable without the SDK tool-call layer.
  • Per-item error isolation — one failing read/exec returns its own error entry; the batch never aborts.
  • Bounded — concurrency semaphore + a 50-item cap so a large batch can't swamp the sandbox.
  • Session accessed via the same ctx.context["sandbox_session"] convention the existing tools use; registered in _BASE_TOOLS so 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. ruff clean.

🤖 Generated with Claude Code

… 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-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds concurrent sandbox tools for batched file reads and shell commands. The main changes are:

  • Registers both tools for root and child agents.
  • Runs up to eight file reads or commands concurrently.
  • Preserves input order and isolates per-item failures.
  • Adds fake-session tests for concurrency, limits, and error handling.

Confidence Score: 4/5

Workspace traversal and valid batches exceeding their tool deadlines need to be fixed before merging.

  • workspace/../ inputs can resolve outside /workspace.
  • Fifty slow items can require about 420 seconds, longer than either enclosing tool deadline.
  • Registration and basic result ordering follow existing patterns.

strix/tools/batch/tools.py

Security Review

The new file reader accepts parent-directory traversal after the workspace/ prefix, allowing reads outside the documented workspace root.

Important Files Changed

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

Comment thread strix/tools/batch/tools.py Outdated
Comment on lines +58 to +59
target = rel if rel.startswith("workspace/") else f"workspace/{rel}"
result = await session.exec("cat", f"/{target}", timeout=_DEFAULT_TIMEOUT)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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>
@seanturner83

Copy link
Copy Markdown
Contributor Author

Thanks — both P1s were real, fixed in 659f643:

  1. Workspace traversal — added _safe_workspace_path(): it normalises the path and rejects anything resolving outside /workspace (returned as a per-item error, never passed to cat). Safe entries in the same batch still read. Tests cover ../../etc/passwd, workspace/../secret, and prefix normalisation.

  2. Batches exceeding the tool deadline — you're right: ceil(50/8)=7 waves × 60s ≈ 420s blew past the 180s/300s tool timeouts. Lowered per-item timeouts (view 20s, exec 40s) so 7 waves stay under each tool deadline, and named all four constants with the invariant spelled out + a test asserting waves * per_item < tool_deadline.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant