Both review passes on #13 noticed this and neither filed it, because the fix is a design decision rather than a patch.
The gap
readWorkspace walks the source with readdir(root, { recursive: true }) and reads every file into memory as a string. seedWorkspace then writes each one through writeTextFile, which in the harness layer is intoDirectoryFor (an mkdir) followed by writeFile — so on the Docker backend that is a docker round trip per directory plus one per file.
There is no ignore list, no size cap, and no batching.
Why it matters more than it looks
This is not a performance nit on an edge case. It blocks the use case the project states as its reason to exist. From packages/core/src/agent/workspace.ts own header:
a directory handed to defineAgent({ workspace }) is not a file drop — it is how an existing Claude Code project is carried into a sandbox unchanged.
Point workspace at an actual Claude Code project and it walks node_modules, .git, dist, and every build artifact into memory, then issues two container calls for each.
And since #13 it is a hard failure, not merely slow. decodeText now refuses a file that is not valid UTF-8, naming it — deliberately, so a PNG cannot be silently seeded as a string of U+FFFD. A real project directory contains at least one such file (a .git pack, an icon, a lockfile binary), so readWorkspace rejects the whole seed. The UTF-8 guard is right; combined with the missing ignore rules it means the documented use case currently cannot run at all.
What needs deciding
- Which ignore rules. Honour the project
.gitignore? A fixed built-in list (node_modules, .git, dist)? Caller-supplied globs? Some combination, and which wins.
- What to do with a binary file once ignores exist — still refuse, or skip with a warning? The refusal is correct for a file the caller meant to seed and wrong for one that only got picked up by a broad walk.
- A size cap, per file and in total, and whether exceeding it refuses or warns.
- Whether seeding should batch rather than issuing per-file calls — this is a backend question (a tar stream into the container), so it may belong with the sandbox contract rather than here.
Scope note
The Worker path takes a WorkspaceFiles record inlined at build time and never calls readWorkspace, so whatever is decided here also has to be what the future build-time inliner applies — otherwise local runs and deployed ones carry different file sets.
Both review passes on #13 noticed this and neither filed it, because the fix is a design decision rather than a patch.
The gap
readWorkspacewalks the source withreaddir(root, { recursive: true })and reads every file into memory as a string.seedWorkspacethen writes each one throughwriteTextFile, which in the harness layer isintoDirectoryFor(an mkdir) followed bywriteFile— so on the Docker backend that is a docker round trip per directory plus one per file.There is no ignore list, no size cap, and no batching.
Why it matters more than it looks
This is not a performance nit on an edge case. It blocks the use case the project states as its reason to exist. From
packages/core/src/agent/workspace.tsown header:Point
workspaceat an actual Claude Code project and it walksnode_modules,.git,dist, and every build artifact into memory, then issues two container calls for each.And since #13 it is a hard failure, not merely slow.
decodeTextnow refuses a file that is not valid UTF-8, naming it — deliberately, so a PNG cannot be silently seeded as a string of U+FFFD. A real project directory contains at least one such file (a.gitpack, an icon, a lockfile binary), soreadWorkspacerejects the whole seed. The UTF-8 guard is right; combined with the missing ignore rules it means the documented use case currently cannot run at all.What needs deciding
.gitignore? A fixed built-in list (node_modules,.git,dist)? Caller-supplied globs? Some combination, and which wins.Scope note
The Worker path takes a
WorkspaceFilesrecord inlined at build time and never callsreadWorkspace, so whatever is decided here also has to be what the future build-time inliner applies — otherwise local runs and deployed ones carry different file sets.