Skip to content

feat(harness): keep oversized bash output inside the sandbox - #56

Merged
sanchitrk merged 3 commits into
canaryfrom
claude/sandbox-output-spill
Oct 9, 2026
Merged

sanchitrk merged 3 commits into
canaryfrom
claude/sandbox-output-spill

Conversation

@sanchitrk

@sanchitrk sanchitrk commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Third of three stacked PRs — based on #55. Merge #54 and #55 first.

What

  • The bash tool's full output used to go to a temp file on the harness host, which a remote sandbox (and the model's tools in it) cannot read. The command now runs in a POSIX sh wrapper (tool/output.ts) that writes its combined output to a file under the mount's spill path, created 0600 and exclusively, kept only when the raw output exceeds the display limits.
    • Streaming backends (host, Vercel) tee it; output still flows back live.
    • Buffered backends (Daytona, just-bash) return only a header (size, newline count, kept flag) and a tail window, so a huge output never crosses to the harness; the skipped bytes are counted (Accumulator.skip).
  • spillPath on the mount identity (SandboxIO.Current): /tmp by default; Daytona and Vercel define SPILL_PATH and record it in their runtime config like DEFAULT_CWD, so it can become configurable later.
  • The host sandbox shell now streams and, when a command is cut short, escalates SIGTERM → SIGKILL for the whole process group. This replaces ToolShell.local, which no plugin used since bash moved onto SandboxIO.Shell.
  • A timed-out or cancelled call removes the files its wrapper never got to clean up.

Testing

  • test/tools.output.test.ts: the bash tool end to end over the host shell (streaming), the host shell without streaming (buffered under a real sh) and just-bash — no file for small output, complete file + correct tail/line numbers for large output (with non-ASCII), 0600, ) in a comment and exit, background jobs still writing, a huge final line, invalid UTF-8, a trailing NUL, deadlines, cancellation, and an orphaned child that ignores SIGTERM.
  • Faults injected for each fix fail the suite.
  • Full non-live suite: 548 passed. The full-harness bash spec now reads the spill file through a mount of the sandbox; the full live suite (Daytona + Vercel, 42 tests) passes on the final code.

🤖 Generated with Claude Code

@sanchitrk
sanchitrk force-pushed the claude/sandbox-output-spill branch from 0689d1e to aa7e176 Compare October 9, 2026 14:12
@sanchitrk
sanchitrk added this pull request to stack #58 October 9, 2026 14:14
@sanchitrk
sanchitrk force-pushed the claude/sandbox-output-spill branch from aa7e176 to 8719caf Compare October 9, 2026 14:14
Base automatically changed from claude/sandbox-scan-lines to canary October 9, 2026 14:15
The bash tool's full output used to go to a temp file on the harness host,
which a remote sandbox cannot read. The command now runs in a POSIX sh
wrapper that writes its output to a file under the mount's spill path
(SandboxIO.Current.spillPath, /tmp unless the provider sets one), created
0600 and exclusively, and kept only when the output exceeds the display
limits. Streaming backends tee it; buffered backends return only a header
and the tail window, so a huge output never crosses to the harness.

The host sandbox shell now streams and escalates SIGTERM to SIGKILL for the
whole process group on interruption, replacing ToolShell.local, which no
plugin used. Daytona and Vercel record their spill path in the runtime
config like their default cwd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sanchitrk
sanchitrk force-pushed the claude/sandbox-output-spill branch from 8719caf to 7d5d1fb Compare October 9, 2026 14:15
sanchitrk and others added 2 commits October 9, 2026 22:43
The model cannot tell a host from a sandbox: its tools act on one
environment. What it needs is that the full output was saved and where,
which the truncation footer already gives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model saw only the output of a failed command, flagged as an error:
the exit code and the deadline lived in `details`, which it never reads,
so a silent failure or a timeout arrived blank. Append the reason —
"Command exited with code N", "Command timed out after N seconds",
"Command terminated without an exit code" — and show "(no output)" for
empty successful output.

The description now promises the truncation note rather than a path at
the end: no path is given when the output file could not be created or
was not kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sanchitrk
sanchitrk merged commit d010819 into canary Oct 9, 2026
@sanchitrk
sanchitrk deleted the claude/sandbox-output-spill branch October 9, 2026 18:04
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