Skip to content

Add Vercel sandbox provider#700

Open
ColeMurray wants to merge 6 commits into
mainfrom
feat/vercel-managed-base-snapshot
Open

Add Vercel sandbox provider#700
ColeMurray wants to merge 6 commits into
mainfrom
feat/vercel-managed-base-snapshot

Conversation

@ColeMurray
Copy link
Copy Markdown
Owner

@ColeMurray ColeMurray commented Jun 3, 2026

Summary

  • add the Vercel Sandbox provider, REST client, repo image support, snapshot restore, and explicit stop behavior
  • add the managed Vercel base-runtime snapshot build step for Terraform applies when SANDBOX_PROVIDER=vercel
  • document Vercel sandbox setup, managed base snapshots, runtime source, shutdown behavior, and current CPU/RAM defaults

Notes

The provider intentionally leaves Vercel sandbox resources unset. Vercel therefore uses its default sandbox size, currently observed as 2 vCPU / 4 GB RAM. Resource sizing can be added later by threading a resources.vcpus value through the Vercel create-sandbox request.

Validation

  • npm ci
  • npm run build -w @open-inspect/shared
  • npm test -w @open-inspect/control-plane -- src/sandbox/vercel-base-snapshot.test.ts src/sandbox/providers/vercel-provider.test.ts src/sandbox/vercel-client.test.ts src/sandbox/provider-name.test.ts src/sandbox/lifecycle/manager.test.ts
  • npm test -w @open-inspect/web -- src/lib/sandbox-provider.test.ts
  • npm run build:vercel-base-snapshot -w @open-inspect/control-plane
  • npm run typecheck -w @open-inspect/control-plane
  • npm run lint -w @open-inspect/control-plane
  • npm run build -w @open-inspect/control-plane
  • npm run typecheck -w @open-inspect/web
  • npm run lint -w @open-inspect/web
  • terraform fmt -check -recursive terraform
  • terraform -chdir=terraform/environments/production validate -no-color

Note: local Terraform validation required a temporary darwin_arm64 Cloudflare provider checksum from terraform init; that lock-file change was removed before commit.

Summary by CodeRabbit

Release Notes

  • New Features

    • Vercel Sandboxes now available as a sandbox backend option alongside Modal and Daytona
    • Pre-built image support extended to Vercel provider
  • Documentation

    • New comprehensive Vercel Sandbox Provider guide added
    • Updated deployment and configuration documentation to cover Vercel setup, credentials, and runtime settings
  • Infrastructure

    • GitHub Actions workflow enhanced with Vercel base snapshot building capabilities
    • Terraform configuration expanded with Vercel sandbox credentials, runtime configuration, and environment variables

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This pull request adds comprehensive Vercel Sandbox support as an alternative to Modal and Daytona backends. The implementation includes a REST client for the Vercel API, runtime bootstrap tooling, base snapshot management, a full SandboxProvider implementation, and integration into the control plane session lifecycle and repo-image routing, plus Terraform automation and documentation.

Changes

Vercel Sandbox Provider Implementation

Layer / File(s) Summary
Vercel Sandbox REST Client
packages/control-plane/src/sandbox/vercel-client.ts, vercel-client.test.ts
Implements Worker-compatible HTTP client for Vercel Sandbox API with request/response types for sandbox creation, command execution, snapshotting, and NDJSON parsing, plus correlation-aware logging and error wrapping.
Vercel Runtime Bootstrap Script Generation
packages/control-plane/src/sandbox/vercel-bootstrap.ts
Exports constants for Vercel runtime (Python binary path, default Node runtime) and buildVercelBootstrapScript() function that generates a complete shell script for OS/Python/Node setup, runtime repo cloning, git credential configuration, and dependency installation.
Vercel Base Snapshot Builder
packages/control-plane/src/sandbox/vercel-base-snapshot.ts, vercel-base-snapshot.test.ts
Manages creation of persistent base-runtime snapshots, coordinating Vercel Sandbox lifecycle: sandbox creation with OpenInspect tags, bootstrap execution with timeout, snapshot capture, session cleanup, and snapshot name normalization.
Vercel Sandbox Provider Interface
packages/control-plane/src/sandbox/providers/vercel-provider.ts, vercel-provider.test.ts
Full SandboxProvider implementation for Vercel: createSandbox, restoreFromSnapshot, takeSnapshot, stopSandbox, and triggerRepoImageBuild. Includes environment variable composition, SCM credential injection (GitHub/GitLab/Bitbucket), tunnel URL generation, code-server password derivation, and embedded Python build coordinator for repo image builds.
Provider Name Resolution and Capabilities
packages/control-plane/src/sandbox/provider-name.ts, provider-name.test.ts
Extends SandboxBackendName type to include "vercel", adds resolution logic for normalized provider values, and introduces supportsRepoImageBackend() to determine repo image support per backend (true for Modal/Vercel, false for Daytona).
Session Lifecycle with Vercel Wiring
packages/control-plane/src/session/durable-object.ts, src/sandbox/lifecycle/manager.ts, lifecycle/manager.test.ts
Refactors createLifecycleManager() to support three backends (Daytona, Vercel, Modal) with provider-specific env validation and configuration. Updates stop/snapshot decision logic in alarm handling to use canStopProviderSandbox() capability check. Expands repo image lookup to use supportsRepoImageBackend() gating.
Repo Images Multi-Backend Routing
packages/control-plane/src/routes/repo-images.ts
Extends repo-images endpoints to support Modal and Vercel backends: adds provider detection, backend-specific build triggering (Modal client vs. Vercel provider), image cleanup for either backend, and consistent error messaging across all handlers.
Control Plane Exports and Configuration
packages/control-plane/src/sandbox/index.ts, src/types.ts
Re-exports Vercel sandbox client, provider, base-snapshot, and bootstrap types/functions. Extends Env interface with VERCEL_TOKEN and nine Vercel configuration fields (project/team, base snapshot ID, runtime settings, snapshot expiration).
CLI Snapshot Builder
packages/control-plane/package.json, scripts/build-vercel-base-snapshot.ts
Adds npm script build:vercel-base-snapshot that bundles the snapshot builder for Node. CLI script reads Vercel credentials from env, invokes buildVercelBaseSnapshot, and outputs snapshot ID to file or stdout.

Terraform and CI/CD Automation

Layer / File(s) Summary
Terraform Variables and Locals
terraform/environments/production/variables.tf, terraform.tfvars.example, locals.tf
Introduces Vercel sandbox configuration variables (token, project/team IDs, base snapshot ID, runtime settings, snapshot expiration) with validation when sandbox_provider = "vercel". Adds use_vercel_backend local flag and extends sandbox_provider validation to accept "vercel".
Terraform Outputs and Worker Bindings
terraform/environments/production/outputs.tf, workers-control-plane.tf
Exports vercel_base_snapshot_id conditional on Vercel backend. Conditionally adds Vercel credentials and config as worker bindings/secrets when use_vercel_backend is enabled. Updates verification commands to include Vercel-specific health checks.
GitHub Actions Workflow
.github/workflows/terraform.yml
Adds conditional "Build Vercel base snapshot" step to Terraform Apply job (runs when SANDBOX_PROVIDER=vercel), validates Vercel credentials, builds snapshot via npm script, and exports snapshot_id for downstream Terraform variable. Increases Apply job timeout from 20 to 45 minutes.

Documentation and Frontend Updates

Layer / File(s) Summary
Vercel Sandbox Provider Guide
docs/VERCEL_SANDBOX_PROVIDER.md
New comprehensive documentation covering Vercel sandbox configuration inputs, base snapshot creation flow, session startup snapshot precedence, snapshot/shutdown lifecycle, and current CPU/memory resource notes with external references.
Getting Started Multi-Cloud Guide
docs/GETTING_STARTED.md
Expands provider overview table to include Vercel, adds "Required Accounts" row for Vercel, inserts dedicated "Vercel Sandboxes" setup section covering API token/project/team and CI snapshot flow, and extends CI/CD secrets table with Vercel entries.
Architecture Documentation
docs/HOW_IT_WORKS.md
Adds Vercel Sandboxes as a supported backend in the Data Plane section, updates snapshots-performance explanation to include both Modal and Vercel, and documents managed base-runtime snapshot build flow and VERCEL_BASE_SNAPSHOT_ID wiring.
Configuration Guidance
docs/SECRETS.md, docs/IMAGE_PREBUILD.md
Broadens ANTHROPIC_API_KEY guidance and "Model not found" troubleshooting to cover both Daytona and Vercel (not Modal). Clarifies pre-built image availability for Modal and Vercel only.
Terraform README
terraform/README.md
Documents Vercel sandbox setup, base snapshot ID generation in CI, and GitHub Actions secrets required for Vercel deployments.
Web Frontend Provider Support
packages/web/src/lib/sandbox-provider.ts, sandbox-provider.test.ts, src/app/api/repo-images/*, src/components/settings/images-settings.tsx
Updates PublicSandboxProvider type to include "vercel", extends supportsRepoImages() to return true for both Modal and Vercel, and updates error messages across repo-image API routes and settings UI to reflect multi-provider support.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ColeMurray/background-agents#466: Expands existing Daytona secrets guidance (ANTHROPIC_API_KEY, "Model not found" troubleshooting) to also cover Vercel, directly building on the documentation changes in this PR.
  • ColeMurray/background-agents#444: Both PRs extend sandbox-provider capabilities and update provider-based gating logic in packages/control-plane/src/sandbox/lifecycle/manager.ts and packages/control-plane/src/routes/repo-images.ts.
  • ColeMurray/background-agents#687: Overlaps with changes to Modal client configuration and gating logic in packages/control-plane/src/routes/repo-images.ts.

🐰 A sandbox provider hops in with glee,
Vercel's API, now wild and free,
Snapshots persist, runtimes compile,
Multi-cloud backends in style!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Vercel sandbox provider' is clear, concise, and directly summarizes the main objective of the pull request—introducing support for Vercel as a new sandbox backend.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/vercel-managed-base-snapshot

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/control-plane/src/routes/repo-images.ts (1)

84-112: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Authenticate build callbacks before updating D1.

The new Vercel coordinator signs callbacks with Authorization: Bearer <timestamp>.<hmac>, but neither handler validates that header. Anyone who can guess a build_id can mark builds ready/failed, and the success path can cascade into deleting a provider image. Verify the HMAC and timestamp before mutating build state.

Also applies to: 176-195

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/routes/repo-images.ts` around lines 84 - 112,
Extract and validate the Authorization header in both handleBuildComplete and
the corresponding failure handler (handleBuildFailed) before making any DB
mutations: read the raw request body (before parseJsonBody), parse Authorization
of form "Bearer <timestamp>.<hmac>", ensure timestamp is within an allowed
window (e.g., ±5 minutes), compute an HMAC using the shared coordinator secret
from env (e.g., env.COORDINATOR_SECRET) over the same data the coordinator signs
(timestamp + "." + rawBody or timestamp + "." + build_id depending on
coordinator spec), compare the computed HMAC to the provided one using a
constant-time comparison, and return 401 on missing/invalid signature or expired
timestamp; only proceed to parseJsonBody and update D1 when validation succeeds.
🧹 Nitpick comments (1)
packages/control-plane/src/sandbox/providers/vercel-provider.ts (1)

477-483: ⚡ Quick win

Hoist these default command timeouts into named constants.

The new timeout defaults are being restated inline instead of being defined once and reused, which makes drift between provider commands more likely as this file grows.

As per coding guidelines, "Define each default timeout value exactly once as a named constant and import it everywhere rather than restating literal values" and "Do not use bare timeout variable names; always encode the unit (seconds for Python, milliseconds for TypeScript) in the variable name."

Also applies to: 496-501

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/sandbox/providers/vercel-provider.ts` around lines
477 - 483, Hoist the literal timeout values used in calls like
this.client.runCommandAndWait({ sessionId, command: "sudo", args: [...],
timeoutMs: 30_000 }) (and the similar call at lines ~496-501) into a named,
exported constant (e.g. VERCEL_PROVIDER_COMMAND_TIMEOUT_MS) and import/use that
constant instead of the magic number; follow the unit-encoding rule in the
constant name (use _MS for TypeScript timeouts). Update all runCommandAndWait
invocations in vercel-provider.ts to reference the new constant and remove the
inline numeric literals so the default timeout is defined exactly once and
reused.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/terraform.yml:
- Around line 312-348: Update the error message in the "Build Vercel base
snapshot" step so it references the actual env var names used elsewhere
(VERCEL_TOKEN and VERCEL_PROJECT_ID) instead of VERCEL_SANDBOX_TOKEN and
VERCEL_SANDBOX_PROJECT_ID; modify the check block that currently echoes
"VERCEL_SANDBOX_TOKEN and VERCEL_SANDBOX_PROJECT_ID are required when
SANDBOX_PROVIDER=vercel" to echo the correct variable names (VERCEL_TOKEN and
VERCEL_PROJECT_ID) so it matches the env keys set and the build script's
requiredEnv calls.

In `@docs/VERCEL_SANDBOX_PROVIDER.md`:
- Line 25: Update the phrasing in the sentence "For GitHub Actions based
deployment, configure the matching repository secrets:" to hyphenate the
compound modifier; change it to "For GitHub Actions-based deployment, configure
the matching repository secrets:" so the compound adjective "GitHub
Actions-based" is clear and grammatically correct.

In `@packages/control-plane/scripts/build-vercel-base-snapshot.ts`:
- Around line 18-25: The script currently allows a missing or invalid --output
value to silently fall back to stdout; modify getArgValue/main so that when
"--output" is present but no following value exists or the following token
starts with "-" (another flag), the script fails fast: detect this condition
after calling getArgValue("--output") in main (or enhance getArgValue to
validate the next token), print a clear error to stderr (including the fact that
--output is missing/invalid) and exit with a non-zero status (e.g., throw or
process.exit(1)) so CI fails instead of continuing.

In `@packages/control-plane/src/sandbox/providers/vercel-provider.ts`:
- Around line 477-485: The remote tunnel-writer invocation using
this.client.runCommandAndWait(...) (with sessionId, VERCEL_PYTHON_BIN and the
script that calls Path.write_text(...)) currently assumes failures will throw;
instead inspect the result returned by runCommandAndWait and fail the launch on
non-zero exit. Modify the caller around runCommandAndWait to capture its return
value (exitCode / status / stdout/stderr fields), check for a non-zero exit code
or error status, log the remote stderr with context, and throw or return an
error so sandbox launch stops when Path.write_text(...) fails.
- Around line 705-721: The happy-path for event "image_build.complete" snapshots
and calls callback but never shuts down the running sandbox, leaving proc alive
until BUILD_TIMEOUT_SECONDS; after snapshot_session() and before returning,
explicitly stop the session by terminating proc (use
proc.send_signal(signal.SIGTERM) then wait with a short timeout and fall back to
proc.kill() and wait) or call the provider's existing cleanup API (e.g.,
stopSandbox/stopSession) to release resources, and ensure any teardown succeeds
before returning from the handler.
- Around line 309-322: The PATH is hard-coded to /vercel/runtimes/node24/bin in
the env construction (Object.assign block) causing mismatched runtime when
this.providerConfig.runtime (or VERCEL_RUNTIME) differs; update the env builders
that set PATH (the block that assigns HOME, NODE_ENV, PATH, etc., and the
similar block around lines 356-377) to interpolate the configured runtime
instead of node24—use this.providerConfig.runtime (or
config.runtime/VERCEL_RUNTIME) to build the runtimePath (e.g.,
`/vercel/runtimes/${runtime}/bin`) and use that value for PATH so the sandbox
runtime and PATH remain consistent.
- Around line 366-376: The sandbox-wide env currently exposes sensitive
Vercel/control-plane secrets (OI_INTERNAL_CALLBACK_SECRET, OI_VERCEL_TOKEN) via
buildBuildEnvVars(), making them visible to repo builds run by
buildCoordinatorScript(); remove these keys from the sandbox-level environment
and instead inject them only into the coordinator/process that performs remote
API callbacks (e.g., pass secrets into the coordinator runtime or a dedicated
callback handler) so repo build env (env=os.environ.copy() used in
buildCoordinatorScript()) never contains OI_INTERNAL_CALLBACK_SECRET or
OI_VERCEL_TOKEN; update buildBuildEnvVars() to omit those two keys (and the same
for the other occurrences called out), and wire secret access into the specific
functions that perform callback/API calls rather than the repo build
environment.

In `@packages/control-plane/src/sandbox/vercel-bootstrap.ts`:
- Line 73: The generated git credential helper script incorrectly references the
shell variable ${VERCEL_PYTHON_BIN} (a build-time TypeScript constant) which is
not defined at runtime; update the script generation so it embeds the actual
Python binary path value instead of the shell variable. Specifically, change the
string written to /usr/local/bin/oi-git-credentials in the code that constructs
that script so the command uses the resolved VERCEL_PYTHON_BIN constant (the
build-time value) and still calls -m
sandbox_runtime.credentials.git_credential_helper "$@" — ensure the resulting
script contains the hardcoded path rather than a ${...} shell variable
reference.

In `@packages/control-plane/src/sandbox/vercel-client.ts`:
- Around line 236-274: The requestText function currently buffers the entire
response body (in requestText) which causes memory exhaustion for NDJSON command
streams; change requestText to consume response.body as a stream (use
response.body.getReader() or a streaming text/NDJSON parser) and process chunks
incrementally, splitting on newlines, parsing each JSON envelope as it arrives,
retaining only the most recent envelope with envelope.type === "command" (or the
"command" field) and discarding prior ones, and if response.ok is false still
read/skips the streamed body then throw VercelSandboxApiError with the captured
status and last error text; ensure headers and error handling logic around
VercelSandboxApiError and httpStatus remain unchanged.

In `@packages/control-plane/src/session/durable-object.ts`:
- Around line 764-766: RepoImageLookup currently only stores provider_image_id
and base_sha which lets SandboxLifecycleManager call the wrong provider after
SANDBOX_PROVIDER changes; update persistence and lookup logic so RepoImageLookup
includes the backend/provider identifier (e.g., provider_name or backend enum)
and ensure code paths that populate or read repoImageLookup (the block guarded
by supportsRepoImageBackend and any callers in SandboxLifecycleManager) filter
by that backend or delete incompatible rows before using the opaque
provider_image_id; also add migration/cleanup logic when reading existing rows
to clear or ignore entries whose stored backend does not match the active
SANDBOX_PROVIDER.

---

Outside diff comments:
In `@packages/control-plane/src/routes/repo-images.ts`:
- Around line 84-112: Extract and validate the Authorization header in both
handleBuildComplete and the corresponding failure handler (handleBuildFailed)
before making any DB mutations: read the raw request body (before
parseJsonBody), parse Authorization of form "Bearer <timestamp>.<hmac>", ensure
timestamp is within an allowed window (e.g., ±5 minutes), compute an HMAC using
the shared coordinator secret from env (e.g., env.COORDINATOR_SECRET) over the
same data the coordinator signs (timestamp + "." + rawBody or timestamp + "." +
build_id depending on coordinator spec), compare the computed HMAC to the
provided one using a constant-time comparison, and return 401 on missing/invalid
signature or expired timestamp; only proceed to parseJsonBody and update D1 when
validation succeeds.

---

Nitpick comments:
In `@packages/control-plane/src/sandbox/providers/vercel-provider.ts`:
- Around line 477-483: Hoist the literal timeout values used in calls like
this.client.runCommandAndWait({ sessionId, command: "sudo", args: [...],
timeoutMs: 30_000 }) (and the similar call at lines ~496-501) into a named,
exported constant (e.g. VERCEL_PROVIDER_COMMAND_TIMEOUT_MS) and import/use that
constant instead of the magic number; follow the unit-encoding rule in the
constant name (use _MS for TypeScript timeouts). Update all runCommandAndWait
invocations in vercel-provider.ts to reference the new constant and remove the
inline numeric literals so the default timeout is defined exactly once and
reused.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7baa04ca-d251-4aad-84cc-602580fa6bf8

📥 Commits

Reviewing files that changed from the base of the PR and between 64482f0 and 0e0ae20.

📒 Files selected for processing (35)
  • .github/workflows/terraform.yml
  • docs/GETTING_STARTED.md
  • docs/HOW_IT_WORKS.md
  • docs/IMAGE_PREBUILD.md
  • docs/SECRETS.md
  • docs/VERCEL_SANDBOX_PROVIDER.md
  • packages/control-plane/package.json
  • packages/control-plane/scripts/build-vercel-base-snapshot.ts
  • packages/control-plane/src/routes/repo-images.ts
  • packages/control-plane/src/sandbox/index.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.test.ts
  • packages/control-plane/src/sandbox/lifecycle/manager.ts
  • packages/control-plane/src/sandbox/provider-name.test.ts
  • packages/control-plane/src/sandbox/provider-name.ts
  • packages/control-plane/src/sandbox/providers/vercel-provider.test.ts
  • packages/control-plane/src/sandbox/providers/vercel-provider.ts
  • packages/control-plane/src/sandbox/vercel-base-snapshot.test.ts
  • packages/control-plane/src/sandbox/vercel-base-snapshot.ts
  • packages/control-plane/src/sandbox/vercel-bootstrap.ts
  • packages/control-plane/src/sandbox/vercel-client.test.ts
  • packages/control-plane/src/sandbox/vercel-client.ts
  • packages/control-plane/src/session/durable-object.ts
  • packages/control-plane/src/types.ts
  • packages/web/src/app/api/repo-images/[owner]/[name]/toggle/route.ts
  • packages/web/src/app/api/repo-images/[owner]/[name]/trigger/route.ts
  • packages/web/src/app/api/repo-images/route.ts
  • packages/web/src/components/settings/images-settings.tsx
  • packages/web/src/lib/sandbox-provider.test.ts
  • packages/web/src/lib/sandbox-provider.ts
  • terraform/README.md
  • terraform/environments/production/locals.tf
  • terraform/environments/production/outputs.tf
  • terraform/environments/production/terraform.tfvars.example
  • terraform/environments/production/variables.tf
  • terraform/environments/production/workers-control-plane.tf

Comment on lines +312 to +348
- name: Build Vercel base snapshot
id: vercel_base_snapshot
run: |
if [ "${SANDBOX_PROVIDER}" != "vercel" ]; then
echo "snapshot_id=${VERCEL_BASE_SNAPSHOT_ID:-}" >> "$GITHUB_OUTPUT"
echo "Skipping Vercel base snapshot build because SANDBOX_PROVIDER=${SANDBOX_PROVIDER}"
exit 0
fi

if [ -z "${VERCEL_TOKEN:-}" ] || [ -z "${VERCEL_PROJECT_ID:-}" ]; then
echo "VERCEL_SANDBOX_TOKEN and VERCEL_SANDBOX_PROJECT_ID are required when SANDBOX_PROVIDER=vercel"
exit 1
fi

npm run build:vercel-base-snapshot -w @open-inspect/control-plane

output_file="${RUNNER_TEMP}/vercel-base-snapshot-id"
node packages/control-plane/dist/vercel-base-snapshot.js --output "$output_file"
snapshot_id="$(tr -d '\r\n' < "$output_file")"

if [ -z "$snapshot_id" ]; then
echo "Vercel base snapshot builder did not write a snapshot ID"
exit 1
fi

echo "Built Vercel base snapshot: $snapshot_id"
echo "snapshot_id=$snapshot_id" >> "$GITHUB_OUTPUT"
env:
SANDBOX_PROVIDER: "${{ secrets.SANDBOX_PROVIDER || 'modal' }}"
VERCEL_BASE_SNAPSHOT_ID: ${{ secrets.VERCEL_BASE_SNAPSHOT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_SANDBOX_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_SANDBOX_PROJECT_ID }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_SANDBOX_TEAM_ID }}
VERCEL_RUNTIME: "${{ secrets.VERCEL_SANDBOX_RUNTIME || 'node24' }}"
VERCEL_RUNTIME_REPO_URL: "${{ secrets.VERCEL_RUNTIME_REPO_URL || 'https://github.com/ColeMurray/background-agents.git' }}"
VERCEL_RUNTIME_REPO_REF: "${{ secrets.VERCEL_RUNTIME_REPO_REF || 'main' }}"
VERCEL_SANDBOX_API_BASE_URL: ${{ secrets.VERCEL_SANDBOX_API_BASE_URL }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix error message to use correct environment variable names.

The error message at line 322 references VERCEL_SANDBOX_TOKEN and VERCEL_SANDBOX_PROJECT_ID, but the actual environment variables set at lines 342-343 are VERCEL_TOKEN and VERCEL_PROJECT_ID (without the SANDBOX prefix). This matches the naming expected by the build script (context snippet 1 shows requiredEnv("VERCEL_TOKEN") and requiredEnv("VERCEL_PROJECT_ID")).

📝 Proposed fix
-          if [ -z "${VERCEL_TOKEN:-}" ] || [ -z "${VERCEL_PROJECT_ID:-}" ]; then
-            echo "VERCEL_SANDBOX_TOKEN and VERCEL_SANDBOX_PROJECT_ID are required when SANDBOX_PROVIDER=vercel"
+          if [ -z "${VERCEL_TOKEN:-}" ] || [ -z "${VERCEL_PROJECT_ID:-}" ]; then
+            echo "VERCEL_TOKEN and VERCEL_PROJECT_ID are required when SANDBOX_PROVIDER=vercel"
             exit 1
           fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Build Vercel base snapshot
id: vercel_base_snapshot
run: |
if [ "${SANDBOX_PROVIDER}" != "vercel" ]; then
echo "snapshot_id=${VERCEL_BASE_SNAPSHOT_ID:-}" >> "$GITHUB_OUTPUT"
echo "Skipping Vercel base snapshot build because SANDBOX_PROVIDER=${SANDBOX_PROVIDER}"
exit 0
fi
if [ -z "${VERCEL_TOKEN:-}" ] || [ -z "${VERCEL_PROJECT_ID:-}" ]; then
echo "VERCEL_SANDBOX_TOKEN and VERCEL_SANDBOX_PROJECT_ID are required when SANDBOX_PROVIDER=vercel"
exit 1
fi
npm run build:vercel-base-snapshot -w @open-inspect/control-plane
output_file="${RUNNER_TEMP}/vercel-base-snapshot-id"
node packages/control-plane/dist/vercel-base-snapshot.js --output "$output_file"
snapshot_id="$(tr -d '\r\n' < "$output_file")"
if [ -z "$snapshot_id" ]; then
echo "Vercel base snapshot builder did not write a snapshot ID"
exit 1
fi
echo "Built Vercel base snapshot: $snapshot_id"
echo "snapshot_id=$snapshot_id" >> "$GITHUB_OUTPUT"
env:
SANDBOX_PROVIDER: "${{ secrets.SANDBOX_PROVIDER || 'modal' }}"
VERCEL_BASE_SNAPSHOT_ID: ${{ secrets.VERCEL_BASE_SNAPSHOT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_SANDBOX_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_SANDBOX_PROJECT_ID }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_SANDBOX_TEAM_ID }}
VERCEL_RUNTIME: "${{ secrets.VERCEL_SANDBOX_RUNTIME || 'node24' }}"
VERCEL_RUNTIME_REPO_URL: "${{ secrets.VERCEL_RUNTIME_REPO_URL || 'https://github.com/ColeMurray/background-agents.git' }}"
VERCEL_RUNTIME_REPO_REF: "${{ secrets.VERCEL_RUNTIME_REPO_REF || 'main' }}"
VERCEL_SANDBOX_API_BASE_URL: ${{ secrets.VERCEL_SANDBOX_API_BASE_URL }}
- name: Build Vercel base snapshot
id: vercel_base_snapshot
run: |
if [ "${SANDBOX_PROVIDER}" != "vercel" ]; then
echo "snapshot_id=${VERCEL_BASE_SNAPSHOT_ID:-}" >> "$GITHUB_OUTPUT"
echo "Skipping Vercel base snapshot build because SANDBOX_PROVIDER=${SANDBOX_PROVIDER}"
exit 0
fi
if [ -z "${VERCEL_TOKEN:-}" ] || [ -z "${VERCEL_PROJECT_ID:-}" ]; then
echo "VERCEL_TOKEN and VERCEL_PROJECT_ID are required when SANDBOX_PROVIDER=vercel"
exit 1
fi
npm run build:vercel-base-snapshot -w `@open-inspect/control-plane`
output_file="${RUNNER_TEMP}/vercel-base-snapshot-id"
node packages/control-plane/dist/vercel-base-snapshot.js --output "$output_file"
snapshot_id="$(tr -d '\r\n' < "$output_file")"
if [ -z "$snapshot_id" ]; then
echo "Vercel base snapshot builder did not write a snapshot ID"
exit 1
fi
echo "Built Vercel base snapshot: $snapshot_id"
echo "snapshot_id=$snapshot_id" >> "$GITHUB_OUTPUT"
env:
SANDBOX_PROVIDER: "${{ secrets.SANDBOX_PROVIDER || 'modal' }}"
VERCEL_BASE_SNAPSHOT_ID: ${{ secrets.VERCEL_BASE_SNAPSHOT_ID }}
VERCEL_TOKEN: ${{ secrets.VERCEL_SANDBOX_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_SANDBOX_PROJECT_ID }}
VERCEL_TEAM_ID: ${{ secrets.VERCEL_SANDBOX_TEAM_ID }}
VERCEL_RUNTIME: "${{ secrets.VERCEL_SANDBOX_RUNTIME || 'node24' }}"
VERCEL_RUNTIME_REPO_URL: "${{ secrets.VERCEL_RUNTIME_REPO_URL || 'https://github.com/ColeMurray/background-agents.git' }}"
VERCEL_RUNTIME_REPO_REF: "${{ secrets.VERCEL_RUNTIME_REPO_REF || 'main' }}"
VERCEL_SANDBOX_API_BASE_URL: ${{ secrets.VERCEL_SANDBOX_API_BASE_URL }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/terraform.yml around lines 312 - 348, Update the error
message in the "Build Vercel base snapshot" step so it references the actual env
var names used elsewhere (VERCEL_TOKEN and VERCEL_PROJECT_ID) instead of
VERCEL_SANDBOX_TOKEN and VERCEL_SANDBOX_PROJECT_ID; modify the check block that
currently echoes "VERCEL_SANDBOX_TOKEN and VERCEL_SANDBOX_PROJECT_ID are
required when SANDBOX_PROVIDER=vercel" to echo the correct variable names
(VERCEL_TOKEN and VERCEL_PROJECT_ID) so it matches the env keys set and the
build script's requiredEnv calls.

# vercel_sandbox_team_id = "team_..." # optional for team projects
```

For GitHub Actions based deployment, configure the matching repository secrets:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hyphenate compound modifier for clarity.

Use “GitHub Actions-based deployment” instead of “GitHub Actions based deployment.”

🧰 Tools
🪛 LanguageTool

[grammar] ~25-~25: Use a hyphen to join words.
Context: ...or team projects ``` For GitHub Actions based deployment, configure the matching...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/VERCEL_SANDBOX_PROVIDER.md` at line 25, Update the phrasing in the
sentence "For GitHub Actions based deployment, configure the matching repository
secrets:" to hyphenate the compound modifier; change it to "For GitHub
Actions-based deployment, configure the matching repository secrets:" so the
compound adjective "GitHub Actions-based" is clear and grammatically correct.

Comment on lines +18 to +25
function getArgValue(name: string): string | undefined {
const index = process.argv.indexOf(name);
if (index === -1) return undefined;
return process.argv[index + 1];
}

async function main(): Promise<void> {
const outputPath = getArgValue("--output");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Treat a missing --output value as an error.

If --output is the last token, getArgValue() returns undefined and the script silently falls back to stdout; if the next token is another flag, it will try to use that flag name as a path. Fail fast here so CI does not report success without writing the requested file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/scripts/build-vercel-base-snapshot.ts` around lines 18
- 25, The script currently allows a missing or invalid --output value to
silently fall back to stdout; modify getArgValue/main so that when "--output" is
present but no following value exists or the following token starts with "-"
(another flag), the script fails fast: detect this condition after calling
getArgValue("--output") in main (or enhance getArgValue to validate the next
token), print a clear error to stderr (including the fact that --output is
missing/invalid) and exit with a non-zero status (e.g., throw or
process.exit(1)) so CI fails instead of continuing.

Comment on lines +309 to +322
Object.assign(envVars, {
HOME: "/root",
NODE_ENV: "development",
PATH: "/root/.bun/bin:/usr/local/bin:/usr/bin:/bin:/vercel/runtimes/node24/bin",
PYTHONPATH: "/app",
PYTHONUNBUFFERED: "1",
NODE_PATH: "/usr/lib/node_modules:/usr/local/lib/node_modules",
SANDBOX_ID: config.sandboxId,
CONTROL_PLANE_URL: config.controlPlaneUrl,
SANDBOX_AUTH_TOKEN: config.sandboxAuthToken,
REPO_OWNER: config.repoOwner,
REPO_NAME: config.repoName,
SESSION_CONFIG: JSON.stringify(sessionConfig),
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the configured runtime when composing PATH.

createSandbox() honors this.providerConfig.runtime, but both env builders hard-code /vercel/runtimes/node24/bin. Setting VERCEL_RUNTIME to anything else creates a sandbox with one runtime and a PATH pointing at another or nonexistent runtime directory.

Also applies to: 356-377

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/sandbox/providers/vercel-provider.ts` around lines
309 - 322, The PATH is hard-coded to /vercel/runtimes/node24/bin in the env
construction (Object.assign block) causing mismatched runtime when
this.providerConfig.runtime (or VERCEL_RUNTIME) differs; update the env builders
that set PATH (the block that assigns HOME, NODE_ENV, PATH, etc., and the
similar block around lines 356-377) to interpolate the configured runtime
instead of node24—use this.providerConfig.runtime (or
config.runtime/VERCEL_RUNTIME) to build the runtimePath (e.g.,
`/vercel/runtimes/${runtime}/bin`) and use that value for PATH so the sandbox
runtime and PATH remain consistent.

Comment on lines +366 to +376
IMAGE_BUILD_MODE: "true",
SESSION_CONFIG: JSON.stringify({ branch: config.defaultBranch }),
OI_VERCEL_BUILD_ID: config.buildId,
OI_VERCEL_CALLBACK_URL: config.callbackUrl,
OI_INTERNAL_CALLBACK_SECRET: this.providerConfig.internalCallbackSecret ?? "",
OI_VERCEL_TOKEN: this.providerConfig.token,
OI_VERCEL_TEAM_ID: this.providerConfig.teamId ?? "",
OI_VERCEL_API_BASE_URL: this.providerConfig.apiBaseUrl ?? "https://vercel.com/api",
OI_VERCEL_SNAPSHOT_EXPIRATION_MS: String(
this.providerConfig.snapshotExpirationMs ?? DEFAULT_SNAPSHOT_EXPIRATION_MS
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Do not put Vercel/control-plane credentials in the sandbox-wide env.

buildBuildEnvVars() injects OI_INTERNAL_CALLBACK_SECRET and OI_VERCEL_TOKEN into the sandbox itself, and buildCoordinatorScript() runs the repo build with env=os.environ.copy(). Any repo code executed during the build can read/exfiltrate those values, which lets an untrusted repo forge build callbacks and call the Vercel API outside the intended flow.

Also applies to: 684-691

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/sandbox/providers/vercel-provider.ts` around lines
366 - 376, The sandbox-wide env currently exposes sensitive Vercel/control-plane
secrets (OI_INTERNAL_CALLBACK_SECRET, OI_VERCEL_TOKEN) via buildBuildEnvVars(),
making them visible to repo builds run by buildCoordinatorScript(); remove these
keys from the sandbox-level environment and instead inject them only into the
coordinator/process that performs remote API callbacks (e.g., pass secrets into
the coordinator runtime or a dedicated callback handler) so repo build env
(env=os.environ.copy() used in buildCoordinatorScript()) never contains
OI_INTERNAL_CALLBACK_SECRET or OI_VERCEL_TOKEN; update buildBuildEnvVars() to
omit those two keys (and the same for the other occurrences called out), and
wire secret access into the specific functions that perform callback/API calls
rather than the repo build environment.

Comment on lines +477 to +485
await this.client.runCommandAndWait(
{
sessionId,
command: "sudo",
args: ["-E", VERCEL_PYTHON_BIN, "-c", script],
timeoutMs: 30_000,
},
correlation
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Check the tunnel writer command's exit code.

runCommandAndWait() only throws on transport/parsing failures; a non-zero remote exit still resolves. If the Path.write_text(...) command fails here, sandbox launch continues with missing .tunnels.env state and no surfaced error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/sandbox/providers/vercel-provider.ts` around lines
477 - 485, The remote tunnel-writer invocation using
this.client.runCommandAndWait(...) (with sessionId, VERCEL_PYTHON_BIN and the
script that calls Path.write_text(...)) currently assumes failures will throw;
instead inspect the result returned by runCommandAndWait and fail the launch on
non-zero exit. Modify the caller around runCommandAndWait to capture its return
value (exitCode / status / stdout/stderr fields), check for a non-zero exit code
or error status, log the remote stderr with context, and throw or return an
error so sandbox launch stops when Path.write_text(...) fails.

Comment on lines +705 to +721
elif event == "image_build.complete":
if not head_sha:
head_sha = read_head_sha()
proc.send_signal(signal.SIGTERM)
try:
proc.wait(timeout=20)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=10)
snapshot_id = snapshot_session()
callback("", {
"build_id": build_id,
"provider_image_id": snapshot_id,
"base_sha": head_sha,
"build_duration_seconds": round(time.time() - started, 3),
})
return
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Successful repo-image builds never explicitly stop the Vercel session.

The happy path snapshots the session, posts the callback, and returns immediately. That leaves each successful build sandbox alive until BUILD_TIMEOUT_SECONDS expires, which burns quota/capacity on every build instead of releasing it as soon as the image is ready.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/sandbox/providers/vercel-provider.ts` around lines
705 - 721, The happy-path for event "image_build.complete" snapshots and calls
callback but never shuts down the running sandbox, leaving proc alive until
BUILD_TIMEOUT_SECONDS; after snapshot_session() and before returning, explicitly
stop the session by terminating proc (use proc.send_signal(signal.SIGTERM) then
wait with a short timeout and fall back to proc.kill() and wait) or call the
provider's existing cleanup API (e.g., stopSandbox/stopSession) to release
resources, and ensure any teardown succeeds before returning from the handler.

sudo chmod -R a+rX /app/sandbox_runtime
sudo ${VERCEL_PYTHON_BIN} -m pip install --break-system-packages -e packages/sandbox-runtime || sudo ${VERCEL_PYTHON_BIN} -m pip install -e packages/sandbox-runtime

printf '%s\\n' '#!/bin/sh' 'exec ${VERCEL_PYTHON_BIN} -m sandbox_runtime.credentials.git_credential_helper "$@"' | sudo tee /usr/local/bin/oi-git-credentials >/dev/null
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Undefined shell variable in generated git credential helper.

Line 73 generates a shell script that references ${VERCEL_PYTHON_BIN}, but this is not defined as a shell environment variable in the generated bootstrap script. The TypeScript constant VERCEL_PYTHON_BIN is only available at build-time, not in the runtime shell environment. When the git credential helper is invoked, the shell will expand ${VERCEL_PYTHON_BIN} to an empty string, causing the Python command to fail.

🐛 Proposed fix

Replace the shell variable reference with the actual hardcoded path:

-printf '%s\\n' '#!/bin/sh' 'exec ${VERCEL_PYTHON_BIN} -m sandbox_runtime.credentials.git_credential_helper "$@"' | sudo tee /usr/local/bin/oi-git-credentials >/dev/null
+printf '%s\\n' '#!/bin/sh' 'exec /usr/bin/python3.12 -m sandbox_runtime.credentials.git_credential_helper "$@"' | sudo tee /usr/local/bin/oi-git-credentials >/dev/null
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
printf '%s\\n' '#!/bin/sh' 'exec ${VERCEL_PYTHON_BIN} -m sandbox_runtime.credentials.git_credential_helper "$@"' | sudo tee /usr/local/bin/oi-git-credentials >/dev/null
printf '%s\\n' '#!/bin/sh' 'exec /usr/bin/python3.12 -m sandbox_runtime.credentials.git_credential_helper "$@"' | sudo tee /usr/local/bin/oi-git-credentials >/dev/null
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/sandbox/vercel-bootstrap.ts` at line 73, The
generated git credential helper script incorrectly references the shell variable
${VERCEL_PYTHON_BIN} (a build-time TypeScript constant) which is not defined at
runtime; update the script generation so it embeds the actual Python binary path
value instead of the shell variable. Specifically, change the string written to
/usr/local/bin/oi-git-credentials in the code that constructs that script so the
command uses the resolved VERCEL_PYTHON_BIN constant (the build-time value) and
still calls -m sandbox_runtime.credentials.git_credential_helper "$@" — ensure
the resulting script contains the hardcoded path rather than a ${...} shell
variable reference.

Comment on lines +236 to +274
private async requestText(
path: string,
init: RequestInit,
correlation: CorrelationContext | undefined,
endpoint: string
): Promise<string> {
const startTime = Date.now();
let httpStatus: number | undefined;
let outcome: "success" | "error" = "error";

try {
const url = new URL(`${this.apiBaseUrl}${path}`);
if (this.config.teamId) {
url.searchParams.set("teamId", this.config.teamId);
}

const headers = new Headers(init.headers);
headers.set("Authorization", `Bearer ${this.config.token}`);
headers.set("Content-Type", headers.get("Content-Type") || "application/json");
headers.set("User-Agent", USER_AGENT);
if (correlation?.trace_id) headers.set("x-trace-id", correlation.trace_id);
if (correlation?.request_id) headers.set("x-request-id", correlation.request_id);
if (correlation?.session_id) headers.set("x-session-id", correlation.session_id);
if (correlation?.sandbox_id) headers.set("x-sandbox-id", correlation.sandbox_id);

const response = await fetch(url.toString(), { ...init, headers });
httpStatus = response.status;
const text = await response.text();
if (!response.ok) {
throw new VercelSandboxApiError(
`Vercel Sandbox API error: ${response.status} ${text}`,
response.status,
text
);
}

outcome = "success";
return text;
} finally {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Stream waited command output instead of buffering the whole body.

requestText() reads the entire NDJSON stream into memory before parsing it. That path is already used by packages/control-plane/src/sandbox/providers/vercel-provider.ts during runtime bootstrap, so a verbose install can exhaust Worker memory and fail bootstrap/base-snapshot creation even when the remote command itself succeeds. Parse the response incrementally and keep only the latest command envelope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/sandbox/vercel-client.ts` around lines 236 - 274,
The requestText function currently buffers the entire response body (in
requestText) which causes memory exhaustion for NDJSON command streams; change
requestText to consume response.body as a stream (use response.body.getReader()
or a streaming text/NDJSON parser) and process chunks incrementally, splitting
on newlines, parsing each JSON envelope as it arrives, retaining only the most
recent envelope with envelope.type === "command" (or the "command" field) and
discarding prior ones, and if response.ok is false still read/skips the streamed
body then throw VercelSandboxApiError with the captured status and last error
text; ensure headers and error handling logic around VercelSandboxApiError and
httpStatus remain unchanged.

Comment on lines +764 to +766
// Create repo image lookup if D1 is available and the provider supports repo images.
let repoImageLookup: RepoImageLookup | undefined;
if (this.env.DB && sandboxBackend === "modal") {
if (this.env.DB && supportsRepoImageBackend(sandboxBackend)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persist repo-image backend provenance before broadening this lookup.

RepoImageLookup still only carries { provider_image_id, base_sha }, and SandboxLifecycleManager passes that opaque ID to whichever provider is active. After switching SANDBOX_PROVIDER between Modal and Vercel, existing rows can be restored or deleted with the wrong provider API. Store/filter by backend (or clear incompatible rows) before enabling supportsRepoImageBackend() here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/control-plane/src/session/durable-object.ts` around lines 764 - 766,
RepoImageLookup currently only stores provider_image_id and base_sha which lets
SandboxLifecycleManager call the wrong provider after SANDBOX_PROVIDER changes;
update persistence and lookup logic so RepoImageLookup includes the
backend/provider identifier (e.g., provider_name or backend enum) and ensure
code paths that populate or read repoImageLookup (the block guarded by
supportsRepoImageBackend and any callers in SandboxLifecycleManager) filter by
that backend or delete incompatible rows before using the opaque
provider_image_id; also add migration/cleanup logic when reading existing rows
to clear or ignore entries whose stored backend does not match the active
SANDBOX_PROVIDER.

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