Summary
A gondolin write implementation that ships the whole file as base64 inside a single
sh -lc "...base64 -d..." script embeds that base64 in one execve argument.
Linux caps a single argv string at MAX_ARG_STRLEN (128 KiB), so writing or editing
any file whose base64 exceeds the cap fails at exec time — in practice around
96 KiB of raw content (base64 is ~4/3 the size, plus the surrounding script).
The failure surfaces as a non-obvious exec error, not a "file too large" message.
This affects write implementations built on the shell-and-base64 pattern. The
example as currently checked out writes via vm.fs.writeFile(...) (a library call,
not a shell exec) and is not affected — see "Applicability" below. We hit this
in production on a shell-based write path once a file's base64 crossed the cap.
Observation
The shell-based pattern looks like this:
const b64 = Buffer.from(content, "utf8").toString("base64");
const script = `mkdir -p ${dir}\necho ${b64} | base64 -d > ${file}`;
const r = await vm.exec(["/bin/sh", "-lc", script]); // <-- script is ONE argv string
The entire script string — which contains the full base64 — is passed as a single
argument to /bin/sh. Once b64 pushes the script past 128 KiB, the exec fails.
Observed symptom: the write tool returns something like
write failed (127): ... exec failed. The number is 127/exec failure rather than
a clean error, so it reads like a missing binary rather than an argument-size limit.
Threshold: raw content ~96 KiB (the exact point depends on how much fixed script
text wraps the base64). Files under that write fine, which is why it lurks until a
file grows.
Impact
For a shell-based write path, the write and edit tools silently cap out at ~96 KiB
raw. Any workload that writes or edits larger files (generated code, data files,
long documents) fails at the exec layer with a confusing message, and — because edit
re-writes the whole file — a file that grows past the threshold can no longer be
edited by the tool at all.
Suggested fix
Two options:
-
Preferred: write through the VM filesystem API rather than a shell round-trip,
e.g. vm.fs.writeFile(guestPath, content, { encoding: "utf8" }). This has no argv
limit and is what the checked-out example already does. If the library API is
available, a shell-based writer should switch to it.
-
If a shell round-trip is required (older gondolin builds, or a wrapper that
deliberately stays shell-only), stream the content in chunks instead of one giant
argument. Append <= 96 KiB base64 chunks to a guest-local staging file with
printf (a shell builtin, so the chunk is not itself an execve argument —
only the small enclosing script is), then decode the staging file into place.
Keep the single-round-trip path for small files. See patch.md.
Applicability
- The checked-out example (
packages/coding-agent/examples/extensions/gondolin,
index.ts:103-104) writes via vm.fs.writeFile and does not hit this. File
this only against a copy that uses the sh -lc base64 pattern (e.g. an older or
derived example), or attach the chunked writer as a hardening for anyone who must
stay on the shell path.
- The cap is a property of Linux argv handling, not of gondolin. Whether an older
gondolin example still ships the shell-based writer we could not verify locally.
Patch reference
patch.md in this directory: a drop-in createGondolinWriteOps that keeps the
single round-trip for small files and switches to chunked printf-append staging
for large ones.
Summary
A gondolin write implementation that ships the whole file as base64 inside a single
sh -lc "...base64 -d..."script embeds that base64 in one execve argument.Linux caps a single argv string at
MAX_ARG_STRLEN(128 KiB), so writing or editingany file whose base64 exceeds the cap fails at exec time — in practice around
96 KiB of raw content (base64 is ~4/3 the size, plus the surrounding script).
The failure surfaces as a non-obvious exec error, not a "file too large" message.
This affects write implementations built on the shell-and-base64 pattern. The
example as currently checked out writes via
vm.fs.writeFile(...)(a library call,not a shell exec) and is not affected — see "Applicability" below. We hit this
in production on a shell-based write path once a file's base64 crossed the cap.
Observation
The shell-based pattern looks like this:
The entire
scriptstring — which contains the full base64 — is passed as a singleargument to
/bin/sh. Onceb64pushes the script past 128 KiB, the exec fails.Observed symptom: the write tool returns something like
write failed (127): ... exec failed. The number is127/exec failure rather thana clean error, so it reads like a missing binary rather than an argument-size limit.
Threshold: raw content ~96 KiB (the exact point depends on how much fixed script
text wraps the base64). Files under that write fine, which is why it lurks until a
file grows.
Impact
For a shell-based write path, the write and edit tools silently cap out at ~96 KiB
raw. Any workload that writes or edits larger files (generated code, data files,
long documents) fails at the exec layer with a confusing message, and — because edit
re-writes the whole file — a file that grows past the threshold can no longer be
edited by the tool at all.
Suggested fix
Two options:
Preferred: write through the VM filesystem API rather than a shell round-trip,
e.g.
vm.fs.writeFile(guestPath, content, { encoding: "utf8" }). This has no argvlimit and is what the checked-out example already does. If the library API is
available, a shell-based writer should switch to it.
If a shell round-trip is required (older gondolin builds, or a wrapper that
deliberately stays shell-only), stream the content in chunks instead of one giant
argument. Append
<= 96 KiBbase64 chunks to a guest-local staging file withprintf(a shell builtin, so the chunk is not itself an execve argument —only the small enclosing script is), then decode the staging file into place.
Keep the single-round-trip path for small files. See
patch.md.Applicability
packages/coding-agent/examples/extensions/gondolin,index.ts:103-104) writes viavm.fs.writeFileand does not hit this. Filethis only against a copy that uses the
sh -lc base64pattern (e.g. an older orderived example), or attach the chunked writer as a hardening for anyone who must
stay on the shell path.
gondolin example still ships the shell-based writer we could not verify locally.
Patch reference
patch.mdin this directory: a drop-increateGondolinWriteOpsthat keeps thesingle round-trip for small files and switches to chunked
printf-append stagingfor large ones.