Skip to content

feat(sandbox): deliver chat attachments to sandboxed external agents#2119

Merged
larryro merged 2 commits into
mainfrom
fix/sandbox-chat-attachments-and-convex-reachability
Jun 24, 2026
Merged

feat(sandbox): deliver chat attachments to sandboxed external agents#2119
larryro merged 2 commits into
mainfrom
fix/sandbox-chat-attachments-and-convex-reachability

Conversation

@larryro

@larryro larryro commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

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].png and describes the image (vision).

What changed

Attachment delivery (the reported bug)

  • Stage each attachment into the session container at /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 under bypassPermissions).
  • On-disk names are the human filename (sanitized + de-duped), never the opaque _storage id. 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)

  • Root cause: the session daemon's undici fetch ignores the egress proxy, so it can only reach Convex via the convex alias on the --internal sandbox net. SANDBOX_STORAGE_INTERNAL_BASE_URL fell back to the public SITE_URL, which is unreachable there — broken in prod/docker, and the bun dev LAN-IP override was VPN-fragile.
  • One contract now: http://convex (storage :3210, http-api/integrations :3211), correct by default in prod/docker (convex is dual-homed onto the sandbox net) and bun dev (the convex-relay socat now bridges :3210 too). Deleted the never-reachable public-URL fallback.

Browser preview in tale deploy

  • SANDBOX_BROWSER_VIEW is 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)

  • Restored the missing stop_grace_period on compose.yml platform/sandbox (Docker's 10s default was SIGKILLing in-flight HTTP/SSE + sandbox execs on docker compose up) to match the CLI generators (45s/30s).
  • New parity tests fail CI on the documented drift classes: the sandbox→convex reachability contract, NET_ADMIN on convex/egress (R1.17), and the graceful-shutdown floors — so compose.yml and the CLI generators can't silently diverge again.

Testing

  • attachment_files.test.ts (16) — path/name/dedup/preamble logic
  • public_storage_url.test.ts (5) — sandbox base URLs must stay http://convex, never a public/host origin
  • compose-parity.test.ts (7) — compose.yml ↔ CLI generator parity (reachability, NET_ADMIN, grace floors)
  • build-exec.test.ts--add-dir emission
  • Full gate green: platform tsc 0 / lint 0 / 113 area tests · CLI tsc 0 / 47 tests · compose parses
  • Live E2E: image upload → agent reads + describes the file

Notes

  • Sandbox/external-agent isn't in prod yet, so no data migration.
  • The full compose single-source generation (generate compose.yml from 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.

larryro added 2 commits June 24, 2026 21:20
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.
@larryro larryro merged commit 301256f into main Jun 24, 2026
55 checks passed
@larryro larryro deleted the fix/sandbox-chat-attachments-and-convex-reachability branch June 24, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant