You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] Windows 宿主上 write_file / edit_file 必然失败:命令中的双引号在传参往返中被丢弃 #3262
Category: bug / area/harness (sandbox filesystem) | Status: Confirmed — the quoted-space command construction in BaseSandboxFilesystem is real on main, and the discarded error output matches the reported "failure without reason" symptom.
Verified against main @ 667590c99 (static review; the Windows + docker.exe argv round-trip itself was not executed here — see Caveats).
1. The quoted-space commands exist exactly as described.
write() builds ... mkdir -p "$(dirname <path>)" 2>&1 — double quotes containing a space, passed as one argv element all the way to sh -c: BaseSandboxFilesystem.java:197-204 (the literal "\"$(dirname " is at :201).
edit() embeds a whole multi-line Python program inside python3 -c "..." with unquoted spaces on every line: BaseSandboxFilesystem.java:258-283.
Execution path: DockerSandbox.doExec passes the command as a single argv element to docker exec ... sh -c <command> (DockerSandbox.java:150-161) via ProcessBuilder(List<String>). On POSIX hosts the JVM quotes each element back into the command line and docker.exe's re-parse round-trips cleanly; on a Windows host the argv → command-line-string → re-parse round-trip is exactly where the inner double quotes (and the spaces they protected) degrade into argv separators, as the reporter's minimum reproduction shows.
2. The bare, reason-less failure message is real.write() returns WriteResult.fail("Failed to write file '" + filePath + "'") when the pre-check exits non-zero — checkResult.output() (which carries the container's dash: Syntax error: end of file unexpected (expecting ")")) is discarded at BaseSandboxFilesystem.java:214. Note line :228 (the upload failure branch) does append the error, so dropping checkResult.output() at :214 is the outlier, not the convention.
3. Why only write/edit break.ls/read/grep/find quote arguments via FilesystemUtils.shellQuote (single quotes, FilesystemUtils.java:101-106) and never produce a double-quoted span containing a space (BaseSandboxFilesystem.java:73-80, :138, :159-163, :372-378), so they survive the round-trip unchanged — matching the reported asymmetry.
4. The project already knows about this OS difference.ShellExecuteTool.commandWithWorkingDirectory(..., boolean windows) branches on os.name for quoting (ShellExecuteTool.java:72-77, :92-98), and mv() already uses the quote-free form mkdir -p $(dirname <escaped>) (BaseSandboxFilesystem.java:433) — i.e. suggested fix (a) is literally the existing convention elsewhere in the same file.
Impact: deterministic failure of write_file/edit_file for any Windows host driving the docker sandbox backend (every quoted-space construct is broken, and edit()'s python3 -c script is the most fragile); non-Windows hosts unaffected.
Fix assessment (agree with a + d as the minimal complete change):
(a) write(): d=$(dirname <path>); mkdir -p "$d" — removes the quoted space; no behavior change on POSIX.
(b/c) edit(): a quoted-space-free rewrite is not sufficient — the script contains many unquoted spaces and newlines, so route the payload via stdin (python3 - <<'PY' ... or docker exec -i ... sh -s), reusing the existing base64-payload plumbing.
(d) append checkResult.output() at :214 (one line, removes the "silent failure" class entirely).
Tests: extend BaseSandboxFilesystemTest to assert no constructed command contains a double-quoted span with a space (cheap regression guard for all current and future call sites).
Caveats: the Windows command-line round-trip was reproduced by the reporter, not re-executed in this evaluation; the code-side evidence above is consistent with their minimum repro, and the fix is safe regardless of host OS. This is a self-contained harness fix — filing a PR is welcome.
I'd like to take this — implementing the evaluation's (a) + (d) minimal scope:
(a) write(): replace the quoted-space mkdir -p "$(dirname <p>)" construction with d=$(dirname <p>); mkdir -p "$d" — the same quote-free-spread pattern move() already uses in the same file.
(d) write(): append the discarded checkResult.output() to the failure message so the container's error text (e.g. the dash syntax error) is visible instead of a bare Failed to write file '...'.
Out of scope for this PR (matching the evaluation): the edit() heredoc/stdin rewrite — that overlaps the in-flight #3084 / #3226, so I'll leave it to those. I'll add a regression test asserting no constructed write() command contains a double-quoted span with a space.
PR submitted: #3269 — implements the agreed (a)+(d) scope: d=$(dirname <p>); mkdir -p "$d" (same pattern move() already uses) plus the discarded checkResult.output() now surfaced in the failure message. Three new all-platform regression tests, sabotage-verified (restoring the old construction fails the new test). edit() left to the in-flight #3084 / #3226 as discussed.
+1, still reproducible on 2.0.4 (Windows 11 host, Docker Desktop, JDK 25).
DockerSandbox builds docker exec ... sh -c <command> with ProcessBuilder (DockerSandbox.java#L150-L160). On Windows, the JDK joins the argument list into one command line without escaping embedded double quotes, so echo "a b" reaches the container as echo a.
As a result, every file write fails. BaseSandboxFilesystem#write first runs the pre-check mkdir -p "$(dirname '<path>')" (BaseSandboxFilesystem.java#L195-L214). The quotes break that check, and its output is discarded, so all you get back is a generic Failed to write file '…'. The new docker cp-based SandboxFileTransfer in 2.0.4 does not help, because write() still runs that pre-check first.
Our workaround: base64-encode the command, because the base64 alphabet contains no quotes, and decode it inside the container. The script is fed through fd 3 while stdin stays on /dev/null:
echo<b64>| base64 -d | sh /dev/fd/3 3<&0 0</dev/null
The plain echo <b64> | base64 -d | sh would feed the script through stdin. Any command in it that reads stdin would then consume the rest of the script, and callers that expect an empty stdin would behave differently. This works unchanged on Linux and Windows hosts, and it needs only base64 and /dev/fd, which Debian/Ubuntu, BusyBox and the Docker default images all provide.
Cross-linking: this is the same root cause as #2924 (Windows ProcessBuilder/cmd.exe argument re-parsing strips nested double quotes from the sh -c payload). A fix is up in PR #3383 — the Windows path now feeds the script to docker exec -i ... sh via stdin instead of the command line, so quotes survive. If you can, please test PR #3383's branch on your setup (Windows 11 + Docker Desktop) and confirm it resolves this too — it would let the maintainers close both issues with one fix.
Activity
oss-maintainer commentedon Sep 23, 2026
Issue Evaluation
Category:
bug/area/harness(sandbox filesystem) | Status: Confirmed — the quoted-space command construction inBaseSandboxFilesystemis real onmain, and the discarded error output matches the reported "failure without reason" symptom.Verified against
main@667590c99(static review; the Windows + docker.exe argv round-trip itself was not executed here — see Caveats).1. The quoted-space commands exist exactly as described.
write()builds... mkdir -p "$(dirname <path>)" 2>&1— double quotes containing a space, passed as one argv element all the way tosh -c:BaseSandboxFilesystem.java:197-204(the literal"\"$(dirname "is at:201).edit()embeds a whole multi-line Python program insidepython3 -c "..."with unquoted spaces on every line:BaseSandboxFilesystem.java:258-283.DockerSandbox.doExecpasses the command as a single argv element todocker exec ... sh -c <command>(DockerSandbox.java:150-161) viaProcessBuilder(List<String>). On POSIX hosts the JVM quotes each element back into the command line anddocker.exe's re-parse round-trips cleanly; on a Windows host the argv → command-line-string → re-parse round-trip is exactly where the inner double quotes (and the spaces they protected) degrade into argv separators, as the reporter's minimum reproduction shows.2. The bare, reason-less failure message is real.
write()returnsWriteResult.fail("Failed to write file '" + filePath + "'")when the pre-check exits non-zero —checkResult.output()(which carries the container'sdash: Syntax error: end of file unexpected (expecting ")")) is discarded atBaseSandboxFilesystem.java:214. Note line:228(the upload failure branch) does append the error, so droppingcheckResult.output()at:214is the outlier, not the convention.3. Why only write/edit break.
ls/read/grep/findquote arguments viaFilesystemUtils.shellQuote(single quotes,FilesystemUtils.java:101-106) and never produce a double-quoted span containing a space (BaseSandboxFilesystem.java:73-80,:138,:159-163,:372-378), so they survive the round-trip unchanged — matching the reported asymmetry.4. The project already knows about this OS difference.
ShellExecuteTool.commandWithWorkingDirectory(..., boolean windows)branches onos.namefor quoting (ShellExecuteTool.java:72-77,:92-98), andmv()already uses the quote-free formmkdir -p $(dirname <escaped>)(BaseSandboxFilesystem.java:433) — i.e. suggested fix (a) is literally the existing convention elsewhere in the same file.Impact: deterministic failure of
write_file/edit_filefor any Windows host driving the docker sandbox backend (every quoted-space construct is broken, andedit()'spython3 -cscript is the most fragile); non-Windows hosts unaffected.Fix assessment (agree with a + d as the minimal complete change):
write():d=$(dirname <path>); mkdir -p "$d"— removes the quoted space; no behavior change on POSIX.edit(): a quoted-space-free rewrite is not sufficient — the script contains many unquoted spaces and newlines, so route the payload via stdin (python3 - <<'PY' ...ordocker exec -i ... sh -s), reusing the existing base64-payload plumbing.checkResult.output()at:214(one line, removes the "silent failure" class entirely).BaseSandboxFilesystemTestto assert no constructed command contains a double-quoted span with a space (cheap regression guard for all current and future call sites).Caveats: the Windows command-line round-trip was reproduced by the reporter, not re-executed in this evaluation; the code-side evidence above is consistent with their minimum repro, and the fix is safe regardless of host OS. This is a self-contained harness fix — filing a PR is welcome.
Automated evaluation by github-manager-bot
aMetric commentedon Sep 23, 2026
I'd like to take this — implementing the evaluation's (a) + (d) minimal scope:
write(): replace the quoted-spacemkdir -p "$(dirname <p>)"construction withd=$(dirname <p>); mkdir -p "$d"— the same quote-free-spread patternmove()already uses in the same file.write(): append the discardedcheckResult.output()to the failure message so the container's error text (e.g. the dash syntax error) is visible instead of a bareFailed to write file '...'.Out of scope for this PR (matching the evaluation): the
edit()heredoc/stdin rewrite — that overlaps the in-flight #3084 / #3226, so I'll leave it to those. I'll add a regression test asserting no constructedwrite()command contains a double-quoted span with a space.aMetric commentedon Sep 23, 2026
PR submitted: #3269 — implements the agreed (a)+(d) scope:
d=$(dirname <p>); mkdir -p "$d"(same patternmove()already uses) plus the discardedcheckResult.output()now surfaced in the failure message. Three new all-platform regression tests, sabotage-verified (restoring the old construction fails the new test).edit()left to the in-flight #3084 / #3226 as discussed.fix(harness): quote-free-spread mkdir in move() and clamp surfaced er…
fix(harness): null-safe clampDetail and scan both write/move commands (…
fix(harness): align move() error fallback with write() and tail-trunc…
fix(harness): remove quoted-space write command and surface its error…
fix(harness): quote-free-spread mkdir in move() and clamp surfaced er…
fix(harness): null-safe clampDetail and scan both write/move commands (…
fix(harness): align move() error fallback with write() and tail-trunc…
fix(harness): fail-closed move/delete and surrogate-safe tail clamp (a…
littlebird69 commentedon Oct 17, 2026
+1, still reproducible on 2.0.4 (Windows 11 host, Docker Desktop, JDK 25).
DockerSandboxbuildsdocker exec ... sh -c <command>withProcessBuilder(DockerSandbox.java#L150-L160). On Windows, the JDK joins the argument list into one command line without escaping embedded double quotes, soecho "a b"reaches the container asecho a.As a result, every file write fails.
BaseSandboxFilesystem#writefirst runs the pre-checkmkdir -p "$(dirname '<path>')"(BaseSandboxFilesystem.java#L195-L214). The quotes break that check, and its output is discarded, so all you get back is a genericFailed to write file '…'. The newdocker cp-basedSandboxFileTransferin 2.0.4 does not help, becausewrite()still runs that pre-check first.Our workaround: base64-encode the command, because the base64 alphabet contains no quotes, and decode it inside the container. The script is fed through fd 3 while stdin stays on
/dev/null:The plain
echo <b64> | base64 -d | shwould feed the script through stdin. Any command in it that reads stdin would then consume the rest of the script, and callers that expect an empty stdin would behave differently. This works unchanged on Linux and Windows hosts, and it needs onlybase64and/dev/fd, which Debian/Ubuntu, BusyBox and the Docker default images all provide.aiyili commentedon Oct 23, 2026
Cross-linking: this is the same root cause as #2924 (Windows
ProcessBuilder/cmd.exeargument re-parsing strips nested double quotes from thesh -cpayload). A fix is up in PR #3383 — the Windows path now feeds the script todocker exec -i ... shvia stdin instead of the command line, so quotes survive. If you can, please test PR #3383's branch on your setup (Windows 11 + Docker Desktop) and confirm it resolves this too — it would let the maintainers close both issues with one fix.fix(harness): surface sandbox failure detail and fail closed on unkno…
fix(harness): surface sandbox failure detail and fail closed on unkno…