Repository navigation
fix(harness): build sandbox commands without load-bearing quotes - #3329
King52HerTz wants to merge 3 commits into
Conversation
move() ran `mkdir -p $(dirname <path>)` with the substitution unquoted, so a destination whose parent directory contains a space word-split into two arguments: mkdir created garbage directories and mv then aborted with "No such file or directory". Derive the parent in Java instead and quote it as a single argument. write() and edit() wrapped `$(dirname ...)`, and the whole Python edit program, in nested double quotes. A sandbox command travels as one command-line argument, and on a Windows host that argument is re-parsed after serialization, which is where the inner double quotes disappear (issue agentscope-ai#3262). Send the program over stdin and the payload as a base64 environment value so neither command contains a double quote. A write rejected by the sandbox also reported only the file name; it now carries the command output, which is what made agentscope-ai#3262 read as an unexplained tool failure.
The failing assertion is in LocalFilesystemPersonalAssistantExampleTest, which writes MEMORY.md through WorkspaceManager.writeUtf8WorkspaceRelative -> filesystem uploadFiles. Nothing in this branch touches uploadFiles, and move() is only reached via moveSkill. The failure does not reproduce locally (single class, full module, and full reactor all pass), so re-running to separate a real coupling from a timing-dependent flake before changing anything here.
oss-maintainer
left a comment
There was a problem hiding this comment.
Summary
PR #3329 — fix(harness): build sandbox commands without load-bearing quotes. The motivation is well founded (nested double quotes do not survive a Windows host re-serialising the command line, per #3262) and the new CommandConstructionTests are a welcome addition. One regression to settle before merge: the base64 payload now travels as an environment assignment on the command line, which reintroduces an OS argument-length ceiling that the previous stdin heredoc did not have.
Findings
- [Warning]
agentscope-harness/src/main/java/io/agentscope/harness/agent/filesystem/sandbox/BaseSandboxFilesystem.java:262—SAA_EDIT_PAYLOAD='<base64>'is a single argv string; Linux caps that at 128 KiB (MAX_ARG_STRLEN), so largeedit()payloads fail withArgument list too long. Keep the payload on stdin or bound it explicitly.
Verified
- The added
@EnabledOnOs({OS.LINUX, OS.MAC})/@Testannotations are valid —EnabledOnOsandOSare already imported inBaseSandboxFilesystemTest.java, so no compile issue there. - Back-compat looks fine for the
FilesystemUtilsaddition (new overload, existingshellQuotecallers unchanged).
CLA note
license/cla is pending on this head commit, so this is a comment-only review (no approval) — @King52HerTz please sign the CLA.
Automated review by github-manager-bot
| + "with open(path, 'wb') as f: f.write(result.encode('utf-8'))\\n" | ||
| + "print(json.dumps({'count': count}))\\n" | ||
| + "\" 2>&1 <<'__EDIT_EOF__'\n" | ||
| "SAA_EDIT_PAYLOAD='" |
There was a problem hiding this comment.
[Warning] Moving the payload into a single environment-variable assignment puts it on the command line, where Linux's per-string execve limit (MAX_ARG_STRLEN, 128 KiB) applies — the previous heredoc-on-stdin form had no such bound. edit() calls whose base64 payload exceeds ~128 KiB (roughly 96 KiB of JSON content) will now fail with Argument list too long, which is a new failure mode for large edits. Suggest keeping the payload on stdin (and single-quoting / escaping the python3 - program) so the command stays quote-free without reintroducing a size ceiling; if an env-var carrier is intended, please cap the payload and return a clear error above the threshold.
|
CLA Not Signed The Contributor License Agreement (CLA) check is currently pending on this PR ( @King52HerTz please sign the CLA via the CLA assistant badge in the comment above, or visit https://cla-assistant.io/agentscope-ai/agentscope-java. Once signed, the Automated check by github-manager-bot |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Following up on the first CI run, which failed Why this diff is not on that test's path:
What I ran locally, all green on this branch:
The Happy to restructure if a maintainer would rather see this split, and fine with re-running the suite as-is. Could someone trigger a re-run of the harness job on the current head? I do not have permission to re-run it myself. |
|
This PR currently conflicts with git fetch origin
git checkout fix/sandbox-command-quoting
git rebase origin/main
# resolve conflicts, then:
git push --force-with-leaseThis is a one-time reminder. Feel free to @mention me for a re-review once the conflict is resolved. Automated notification by github-manager-bot |
|
Status refresh (no action needed from you) Re-checked the current state of this PR:
Nothing else to do until those two are resolved — this is a status note, not a new request. Automated notification by github-manager-bot |
agentscope-ai#3226 rewrote edit() with a text-block program plus an exit-code check, so this branch now takes main's version of edit() and drops the base64-environment payload approach that overlapped it. Remaining scope: write()/move() parent-dir quoting and write()'s failure message.
|
The
Could a maintainer re-run the failed Windows job, or is |
What this fixes
BaseSandboxFilesystem.move()builtThe command substitution is unquoted, so a destination whose parent directory contains a space word-splits into two arguments. Reproduced locally on macOS with a plain
sh:Two directories named
dstanddirget created as a side effect, and the rename never happens. This is not Windows-specific — it fails on any POSIX host. The parent directory is now derived in Java and passed as one single-quoted argument.Windows host transport (issue #3262)
write()wrapped$(dirname ...)in double quotes. A sandbox command travels as a single argv element, and on a Windows host that element is serialized forCreateProcessand re-parsed bydocker.exe; the report in #3262 shows the inner double quotes gone while single-quoted text survives.write()andmove()are now generated without any double quote - the parent directory is computed in Java (FilesystemUtils.mkdirParent) instead of by a shelldirname.edit()is deliberately left as it is on main: #3226 already rewrote that command with a Java text block plus an exit-code check while this PR was open, and its approach is the one that should stay. Consequently theedit_filehalf of #3262 is still open - that command still nests double quotes around the Python program, so on a Windows host it can lose them the same way. I did not fold that into this PR, to keep the two changes reviewable separately; happy to take it as a follow-up.To be explicit about what I verified and what I did not: the
move()failure and the "no nested double quotes" property are covered by tests that run on Linux/macOS. I have no Windows host, so the Windows round trip itself is untested here - the change removes the construct #3262 identifies as the cause, it does not prove the outcome on Windows.Also
A write rejected by the sandbox returned
Failed to write file '<path>'with the command output discarded, which is why #3262 reads as a tool failing for no reason. It now goes throughexecuteFailureMessage, matching howls/read/grep/glob- and, since #3226,edit()- already report failures.Testing
BaseSandboxFilesystemTest.CommandConstructionTestscovers the spaced-parent move, the absence of nested double quotes in the two generated commands, the failure message, andparentDiredge cases (absolute / relative / root / bare / empty / null). Three separate mutations, each killed by exactly one designated test with no collateral failures:move()line onlymove_destinationParentWithSpace_renamesAndCreatesExactlyThatDirectorymv: rename ... No such file or directorywrite()back to"$(dirname ...)"generatedCommands_avoidNestedDoubleQuoteswrite()'s failure messagewrite_sandboxFailure_surfacesCommandOutputFailed to write file '/workspace/note.txt'Full reactor, same command as CI:
Related, deliberately left out of this PR
if [ -e ... ], heredocs) thatcmd.execannot execute at all, regardless of quoting. Fixing those needs a notion of which shell a backend runs programs in, which is a larger change. Not claimed here.shellSingleQuoteis duplicated in five places (SandboxBackedFilesystem,DaytonaSandbox,E2bSandbox,AgentRunSandbox,SharedSandboxFilesystem) with two different escaping algorithms, and three of them emit'"'"'— which reintroduces double quotes and so carries the same Windows exposure.SharedSandboxFilesystemalso has an unquotedmkdir -p $(dirname ...)like the one fixed here. Happy to follow up with a single shared helper if that is wanted.Partially addresses #3262