feat(sandbox): deliver chat attachments to sandboxed external agents#2119
Merged
larryro merged 2 commits intoJun 24, 2026
Merged
Conversation
Chat attachments were dropped at the external-agent dispatch fork, so the sandboxed Claude Code / OpenCode agent never received uploaded files. Stage each attachment into the session container at /user/uploads/<msgId>/<name>, reference the absolute paths in the prompt, and grant read access via --add-dir so the agent can read (and, for images, see) the real files. Fixing the fetch surfaced a latent cross-environment gap: the session daemon's undici fetch ignores the egress proxy, so it can only reach Convex via the `convex` alias on the sandbox net, but SANDBOX_STORAGE_INTERNAL_BASE_URL fell back to the public SITE_URL (unreachable from the --internal net). Unify the contract on http://convex (storage :3210, http-api :3211): correct by default in prod/docker (convex is dual-homed) and bun dev (the convex-relay socat now bridges :3210 too). Also thread SANDBOX_BROWSER_VIEW through the CLI deploy path so the live browser preview can be enabled in production, and add anti-drift guards: restore the missing stop_grace_period on compose.yml platform/sandbox (10s SIGKILL of in-flight SSE/exec on docker compose up) to match the CLI, and lock compose.yml<->CLI-generator parity (reachability, NET_ADMIN, grace floors) plus the sandbox->convex reachability contract with tests.
…ment mock sandbox_render_document.ts now imports the new export from public_storage_url; the test's vi.mock replaced the whole module, so the missing export made vitest throw. Provide it in the mock.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Chat attachments were dropped at the external-agent dispatch fork, so a sandboxed agent (Claude Code / OpenCode) never received uploaded files — the agent replied "image failed to load." This delivers them end-to-end, and along the way fixes a latent cross-environment reachability gap that the feature surfaced, plus a related prod-config drift class.
Verified live: the agent reads
/user/uploads/<msgId>/[1].pngand describes the image (vision).What changed
Attachment delivery (the reported bug)
/user/uploads/<promptMessageId>/<human-filename>(URL mode — bytes stream storage→daemon, never through the action), reference the absolute paths in the prompt, and grant read access with--add-dir(Claude Code's file tools are cwd-scoped even underbypassPermissions)._storageid. Best-effort: skipped files are surfaced in the prompt, never silently dropped. claude-code only for now (opencode out-of-cwd access is a follow-up).Storage reachability — unified on
http://convex(all 3 run modes)fetchignores the egress proxy, so it can only reach Convex via theconvexalias on the--internalsandbox net.SANDBOX_STORAGE_INTERNAL_BASE_URLfell back to the publicSITE_URL, which is unreachable there — broken in prod/docker, and thebun devLAN-IP override was VPN-fragile.http://convex(storage:3210, http-api/integrations:3211), correct by default in prod/docker (convex is dual-homed onto the sandbox net) andbun dev(theconvex-relaysocat now bridges:3210too). Deleted the never-reachable public-URL fallback.Browser preview in
tale deploySANDBOX_BROWSER_VIEWis now a discoverable, default-off, lockstep toggle in the generated.env, so the live read-only browser mirror can actually be enabled in production (it reaches both the spawner and Convex). Previously the CLI gave no wired way to turn it on.Anti-drift guards (the "two parallel compose sources" smell)
stop_grace_periodoncompose.ymlplatform/sandbox(Docker's 10s default was SIGKILLing in-flight HTTP/SSE + sandbox execs ondocker compose up) to match the CLI generators (45s/30s).NET_ADMINon convex/egress (R1.17), and the graceful-shutdown floors — socompose.ymland the CLI generators can't silently diverge again.Testing
attachment_files.test.ts(16) — path/name/dedup/preamble logicpublic_storage_url.test.ts(5) — sandbox base URLs must stayhttp://convex, never a public/host origincompose-parity.test.ts(7) —compose.yml↔ CLI generator parity (reachability,NET_ADMIN, grace floors)build-exec.test.ts—--add-diremissionNotes
compose.ymlfrom the CLI specs) is intentionally NOT in scope — the parity tests already make the bug class fail CI; the generation rewrite is a clean future PR best done when someone's in the compose layer anyway.