Labsco
vercel logo

benchmark-sandbox

โ˜… 209

by vercel-labs ยท part of vercel-labs/vercel-plugin

Run vercel-plugin eval scenarios in Vercel Sandboxes instead of local WezTerm panels. Provisions ephemeral microVMs with Claude Code + plugin pre-installed, runs benchmark prompts, extracts hook artifacts, and produces coverage reports.

๐Ÿ”Œ This skill ships inside the vercel plugin โ€” install the plugin and you also get 5 slash commands, 3 sub-agents, hooks, an MCP server.

This is the playbook your agent receives when the skill activates โ€” you don't need to read it to use the skill, but it's here to audit before installing.

Benchmark Sandbox โ€” Remote Eval via Vercel Sandboxes

Run benchmark scenarios inside Vercel Sandboxes โ€” ephemeral Firecracker microVMs with node24. Each sandbox gets a fresh Claude Code + Vercel CLI + agent-browser install, the local vercel-plugin uploaded, and runs a 3-phase eval pipeline:

  • Phase 1 (BUILD): Claude Code builds the app with --dangerously-skip-permissions --debug
  • Phase 2 (VERIFY): A follow-up Claude Code session uses agent-browser to walk through user stories, fixing issues until all pass (20 min timeout)
  • Phase 3 (DEPLOY): A third Claude Code session links to vercel-labs, runs vercel deploy, and fixes build errors (up to 3 retries). Deployed apps have deployment protection enabled by default.

Skills are tracked across all 3 phases โ€” each phase may trigger additional skill injections as new files/patterns are created. After each phase, a haiku structured scoring step (claude -p --json-schema --model haiku) evaluates the results as structured JSON.

Proven Working Script

Use run-eval.ts โ€” the proven eval runner:

# Run default scenarios with full 3-phase pipeline
bun run .claude/skills/benchmark-sandbox/run-eval.ts

# With dynamic scenarios from a JSON file (recommended โ€” see "Dynamic Scenarios" below)
bun run .claude/skills/benchmark-sandbox/run-eval.ts --scenarios-file /tmp/my-scenarios.json

# Keep sandboxes alive overnight with public URLs
bun run .claude/skills/benchmark-sandbox/run-eval.ts --keep-alive --keep-hours 8

# Build-only (skip verification and deploy)
bun run .claude/skills/benchmark-sandbox/run-eval.ts --skip-verify --skip-deploy

# Run specific scenarios by slug
bun run .claude/skills/benchmark-sandbox/run-eval.ts --scenarios splitwise-clone,calendly-clone

CLI Flags

FlagDefaultDescription
--concurrency N5Max parallel sandboxes (max 10)
--timeout MS1800000 (30 min)Per-phase timeout in ms
--keep-aliveoffKeep sandboxes running after eval
--keep-hours N8Hours to keep alive (with --keep-alive)
--skip-verifyoffSkip the agent-browser verification phase
--skip-deployoffSkip the Vercel deploy phase
--scenarios a,b,callOnly run specific scenarios by slug
--scenarios-file pathโ€”Load scenarios from a JSON file instead of built-in defaults

Instead of hardcoding tech-specific prompts, generate scenarios dynamically as a JSON file. Prompts should describe real-world apps people want to build using user stories โ€” no tech name-dropping. Let the plugin figure out what Vercel tech to inject.

Scenario JSON Format

[
  {
    "slug": "pet-adoption-board",
    "prompt": "Build me a pet adoption listing board where shelters can post animals...",
    "expectedSkills": ["ai-sdk", "nextjs", "shadcn", "vercel-functions"],
    "userStories": [
      "As a visitor, I can see a grid of pet listings with photos and names",
      "As a visitor, I can click a pet card to see a detail page",
      "As a visitor, I can filter pets by type"
    ]
  }
]

Each scenario needs: slug (string), prompt (string), expectedSkills (string[]), userStories (tuple of exactly 3 strings).

Prompt Design Guidelines

  • Focus on what the user wants, not what tech to use
  • Describe real-world apps that solve real problems with friendly, stylish UX
  • Include AI features naturally (recommendations, analysis, generation)
  • Always end with: "Link the project to my vercel-labs team. After building all files, start the dev server on port 3000 with \npx next dev --port 3000`."`
  • Include storage needs (photos, uploads) to trigger vercel-storage
  • Include scheduled tasks (reminders, cleanup) to trigger cron-jobs
  • Include auth/middleware to trigger routing-middleware

Structured Scoring (Haiku)

Each phase gets a structured JSON score via claude -p --json-schema --model haiku --setting-sources "" running inside the sandbox. This is a separate quick pass โ€” no tools, no hooks โ€” just reads the phase output and returns structured data.

Build Score Schema

{
  "completeness": "complete|partial|minimal|empty",
  "hasApiRoutes": true,
  "hasUIComponents": true,
  "hasAIFeature": true,
  "devServerRunning": true,
  "missingFeatures": ["feature1"],
  "summary": "Brief assessment"
}

Verify Score Schema (per user story)

{
  "stories": [
    { "index": 1, "status": "pass|fail", "reason": "Evidence from output" }
  ]
}

Deploy Score Schema

{
  "deployed": true,
  "url": "https://xxx.vercel.app",
  "buildSucceeded": true,
  "errors": [],
  "summary": "Brief assessment"
}

Important: The claude -p --output-format json response wraps results โ€” the actual schema data is in parsed.structured_output, not the top-level object.

Critical Sandbox Environment Facts

PropertyValue
Home directory/home/vercel-sandbox (NOT /home/user/ or /root/)
Uservercel-sandbox (NOT root)
Claude binary/home/vercel-sandbox/.global/npm/bin/claude
PATH (via sh -c)Includes ~/.global/npm/bin โ€” claude findable by name
Port exposuresandbox.domain(3000) โ†’ https://subdomain.vercel.run
Snapshot persistenceFiles AND npm globals survive snapshot restore โ€” use sandbox.snapshot() โ†’ Sandbox.create({ source: { type: "snapshot", snapshotId } })
SDK version@vercel/sandbox@1.8.0 (v2 beta's named sandbox endpoint returns 404 for this team)
Team tierEnterprise (vercel-labs) โ€” no known sandbox time cap

Key Discoveries (Hard-Won)

  1. Snapshots work: sandbox.snapshot() preserves files AND npm globals. Use it after build to create a restore point before verify/deploy. Note: snapshotting stops the source sandbox โ€” create a new one from the snapshot to continue.
  2. Plugin install: Use npx add-plugin <path> -s project -y --target claude-code โ€” works because claude is in PATH after npm install -g. The --target claude-code flag is required because add-plugin can't auto-detect Claude Code without an initialized ~/.claude/ dir.
  3. File uploads: Use sandbox.writeFiles([{ path, content: Buffer }]) โ€” NOT runCommand heredocs. Heredocs with special characters cause 400 errors from the sandbox API.
  4. Claude flags: Always use --dangerously-skip-permissions --debug. The --debug flag writes to ~/.claude/debug/.
  5. Auth: API key from macOS Keychain (ANTHROPIC_AUTH_TOKEN โ€” a vck_* Vercel Claude Key for AI Gateway), Vercel token from ~/.local/share/com.vercel.cli/auth.json (a vca_* token).
  6. OIDC for sandbox SDK: Run npx vercel link --scope vercel-labs -y + npx vercel env pull once before first use.
  7. Port exposure: Pass ports: [3000] in Sandbox.create() to get a public URL immediately via sandbox.domain(3000). Works on v1.8.0 โ€” URL is assigned at creation time, before anything listens.
  8. extendTimeout: Use sandbox.extendTimeout(ms) to keep sandboxes alive past their initial timeout. Verified working โ€” extends by the requested duration. Use this for overnight keep-alive.
  9. Background commands: runCommand with backgrounded processes (& or nohup) may throw ZodError on v1. Write a script file first, then execute it.
  10. Session cleanup race: The session-end-cleanup.mjs hook deletes /tmp/vercel-plugin-*-seen-skills.d/ on session end. Extract artifacts BEFORE the session completes, or rely on poll history data.
  11. agent-browser works in sandboxes: Install via npm install -g agent-browser. Claude Code can use it for browser-based verification inside the sandbox.
  12. No hobby tier cap: Early 301s timeouts were from lower default timeout values in earlier script iterations, not a tier limitation. Enterprise (vercel-labs) has no known sandbox time cap โ€” sandboxes ran 10+ minutes successfully.
  13. claude -p works inside sandboxes: claude -p --json-schema --output-format json --model haiku works for structured scoring passes. No nesting issue when running inside a sandbox (only fails when running Claude inside Claude on the same machine).
  14. Deploy project naming: ALWAYS use timestamped slugs with minute precision (e.g., pet-adoption-board-202603101853) to avoid collisions when linking to vercel-labs team projects. These are demo projects โ€” we generate many per day. Format: <slug>-<YYYYMMDDHHMM>.

When to Use This vs benchmark-agents

benchmark-agents (WezTerm)benchmark-sandbox
EnvironmentLocal macOS terminal panesRemote Vercel Sandboxes (Amazon Linux)
ParallelismLimited by local resourcesUp to 10 (Hobby) or 2,000 (Pro) concurrent
Session typeInteractive TTY via /bin/zsh -icDirect sh -c invocation (PTY not required)
Artifact accessDirect filesystem (~/.claude/debug/)sandbox.readFile() / poll via runCommand
Port exposurelocalhost:3000Public https://sb-XXX.vercel.run URLs
VerificationManual browser checkAutomated agent-browser in Phase 2
DeployManualAutomated Phase 3 โ†’ permanent *.vercel.app URLs
ScoringManual reviewHaiku structured JSON scoring per phase
Best forManual eval + iteration loopAutomated parallel coverage + verification + deploy runs

How It Works

  1. Create fresh sandbox: Sandbox.create({ runtime: "node24", ports: [3000], env: { ANTHROPIC_API_KEY, ... } }) โ€” no snapshot
  2. Install tools: npm install -g @anthropic-ai/claude-code vercel agent-browser (~20s per sandbox)
  3. Auth Vercel CLI: Write token to ~/.local/share/com.vercel.cli/auth.json
  4. Upload plugin: sandbox.writeFiles() for 80 plugin files, then npx add-plugin
  5. Phase 1 โ€” BUILD: Claude Code builds the app (30 min timeout)
  6. Score build: Haiku evaluates completeness, API routes, UI, AI features
  7. Start dev server: If not already running, start npx next dev --port 3000
  8. Extend timeout: sandbox.extendTimeout() for verify + deploy + keep-alive
  9. Phase 2 โ€” VERIFY: Claude Code uses agent-browser to test user stories (20 min timeout). Prompt tells Claude to start dev server itself if not running.
  10. Score verify: Haiku evaluates each user story as pass/fail with reasons
  11. Re-extract skills: Skills re-collected after verify phase (agent-browser + code fixes trigger more)
  12. Phase 3 โ€” DEPLOY: Claude Code runs vercel link + vercel deploy, fixes build errors (30 min timeout)
  13. Score deploy: Haiku evaluates deploy success, URL extraction, errors
  14. Re-extract skills: Skills re-collected after deploy phase
  15. Write incremental results: Each scenario writes its own result.json immediately on completion (survives crashes)
  16. Extract source archive: source.tar.gz of project files saved locally
  17. Generate report: Markdown report with build/verify/deploy scores, skill coverage, URLs

Sandbox Session Flow (Per Scenario)

Sandbox.create({ runtime: "node24", ports: [3000], env: { ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL, VERCEL_PLUGIN_LOG_LEVEL: "trace" } })
  โ”‚
  โ”œโ”€ npm install -g @anthropic-ai/claude-code vercel agent-browser   (~20s)
  โ”œโ”€ Write Vercel CLI auth token to ~/.local/share/com.vercel.cli/auth.json
  โ”œโ”€ mkdir -p /home/vercel-sandbox/<slug> && npm init -y
  โ”œโ”€ sandbox.writeFiles() โ†’ /home/vercel-sandbox/vercel-plugin/  (80 files, ~945KB)
  โ”œโ”€ npx add-plugin /home/vercel-sandbox/vercel-plugin -s project -y --target claude-code
  โ”‚
  โ”œโ”€ Phase 1: BUILD
  โ”‚   โ”œโ”€ sandbox.writeFiles() โ†’ /tmp/prompt.txt
  โ”‚   โ”œโ”€ claude --dangerously-skip-permissions --debug --settings <path> "$(cat /tmp/prompt.txt)"
  โ”‚   โ”‚   (with AbortSignal.timeout(TIMEOUT_MS))
  โ”‚   โ”œโ”€ Poll every 20s:
  โ”‚   โ”‚   โ”œโ”€ ls /tmp/vercel-plugin-*-seen-skills.d/     (claimed skills)
  โ”‚   โ”‚   โ”œโ”€ cat /tmp/vercel-plugin-*-seen-skills.txt    (seen skills snapshot)
  โ”‚   โ”‚   โ”œโ”€ find ~/.claude/debug -type f                (debug log count)
  โ”‚   โ”‚   โ”œโ”€ find <project> -newer /tmp/prompt.txt       (new project files)
  โ”‚   โ”‚   โ””โ”€ curl localhost:3000                         (port status)
  โ”‚   โ”œโ”€ Extract build artifacts
  โ”‚   โ””โ”€ Haiku build score (structured JSON)
  โ”‚
  โ”œโ”€ Start dev server (if not already running)
  โ”œโ”€ sandbox.extendTimeout(...)
  โ”‚
  โ”œโ”€ Phase 2: VERIFY (if >1 project file exists)
  โ”‚   โ”œโ”€ sandbox.writeFiles() โ†’ /tmp/verify.txt  (agent-browser verification prompt)
  โ”‚   โ”œโ”€ claude --dangerously-skip-permissions --debug "$(cat /tmp/verify.txt)"
  โ”‚   โ”‚   (with AbortSignal.timeout(1_200_000) โ€” 20 min)
  โ”‚   โ”œโ”€ Re-extract skills (verify phase triggers more)
  โ”‚   โ””โ”€ Haiku verify score (per-story pass/fail JSON)
  โ”‚
  โ”œโ”€ Phase 3: DEPLOY (if >3 project files)
  โ”‚   โ”œโ”€ sandbox.writeFiles() โ†’ /tmp/deploy.txt
  โ”‚   โ”œโ”€ claude --dangerously-skip-permissions --debug "$(cat /tmp/deploy.txt)"
  โ”‚   โ”‚   (links to vercel-labs, deploys, fixes build errors up to 3x)
  โ”‚   โ”œโ”€ Extract deploy URL from output (*.vercel.app)
  โ”‚   โ”œโ”€ Re-extract skills (deploy phase triggers more)
  โ”‚   โ””โ”€ Haiku deploy score (structured JSON)
  โ”‚
  โ”œโ”€ Write <slug>/result.json immediately (crash-safe)
  โ”œโ”€ Update aggregate results.json (complete: false until all done)
  โ”œโ”€ Extract source.tar.gz
  โ””โ”€ sandbox.stop()  (skipped if --keep-alive)

Verification Phase Details

The verify phase is the "closer" โ€” its job is to make the app work and prove it. Key behaviors:

  • Always runs if >1 project file exists (no longer gated on port 3000 being up)
  • Starts dev server itself if not already running โ€” the prompt tells Claude to check localhost:3000 and run npx next dev --port 3000 if needed
  • 20 minute timeout โ€” enough for agent-browser to open pages, screenshot, interact, fix broken code, restart server, and re-verify
  • Triggers skill injection โ€” the verify session creates/edits files, triggering PreToolUse and PostToolUse hooks
  • Uses agent-browser workflow: open โ†’ wait --load networkidle โ†’ screenshot --annotate โ†’ snapshot -i โ†’ interact โ†’ fix โ†’ re-verify
  • Results scored by haiku โ€” no more parsing STORY_1: PASS from free text

DO NOT (Hard Rules)

Same rules as benchmark-agents, plus sandbox-specific:

  • DO NOT use claude --print or -p flag for BUILD/VERIFY/DEPLOY phases โ€” hooks don't fire without tool-calling sessions (use -p only for haiku scoring passes)
  • DO NOT let sandboxes run without extracting artifacts โ€” ephemeral filesystem is lost on stop
  • DO NOT pass API keys via writeFiles() โ€” use Sandbox.create({ env: { ... } })
  • DO NOT skip snapshotting after build โ€” it's your safety net if verify/deploy kills the sandbox
  • DO NOT use v2 beta SDK โ€” named sandbox endpoint returns 404 for this team; use v1.8.0
  • DO NOT use runCommand heredocs to write file content โ€” use sandbox.writeFiles() instead
  • DO NOT assume /home/user/ exists โ€” the home dir is /home/vercel-sandbox/
  • DO NOT use simple project names without timestamps โ€” always append -YYYYMMDDHHMM to avoid collisions across runs

Commands

# Generate scenarios as JSON, then run
bun run .claude/skills/benchmark-sandbox/run-eval.ts --scenarios-file /tmp/my-scenarios.json

# With all phases + keep-alive for overnight
bun run .claude/skills/benchmark-sandbox/run-eval.ts --scenarios-file /tmp/scenarios.json --keep-alive --keep-hours 8

# Build-only, no verification or deploy
bun run .claude/skills/benchmark-sandbox/run-eval.ts --scenarios-file /tmp/scenarios.json --skip-verify --skip-deploy

# Filter to specific slugs from file or defaults
bun run .claude/skills/benchmark-sandbox/run-eval.ts --scenarios splitwise-clone,calendly-clone

Artifact Export Layout

Results are written to ~/dev/vercel-plugin-testing/sandbox-results/<run-id>/:

<run-id>/
  results.json             # Aggregate results (complete: false until all done, then true)
  report.md                # Markdown report with scores, coverage, URLs
  <slug>/
    result.json            # Per-scenario result (written immediately on completion)
    source.tar.gz          # Project source archive

Each scenario result includes:

  • slug, sandboxId, success, durationMs
  • claimedSkills[], expectedSkills[], projectFiles[]
  • appUrl โ€” public https://sb-XXX.vercel.run URL (sandbox lifetime only)
  • deployUrl โ€” permanent https://xxx.vercel.app URL (if deploy succeeded)
  • pollHistory[] โ€” timestamped skill/file/port snapshots
  • verification โ€” { ran, exitCode, stories: [{ index, status }], output }
  • buildScore โ€” haiku structured completeness assessment
  • deployScore โ€” haiku structured deploy assessment

The markdown report (report.md / .reports/<timestamp>.md) includes:

  1. Summary table โ€” slug, build status, skills, files, verify results, deploy URL, duration
  2. Per-scenario details โ€” build score, deploy score, verification per-story pass/fail
  3. Skill coverage โ€” expected vs actual per scenario, missing/bonus breakdown
  4. Total unique skills across all scenarios

Proven Results (2026-03-10)

Across 34 scenarios run in 5 batches:

MetricBestTypical
Skills per scenario31 (ai-interior-designer)12-24
Expected skill coverage100% (pet-adoption-board 4/4, apartment-hunting-copilot 7/7, splitwise-clone 6/6)50-86%
User stories verified3/3 PASS (ai-dream-journal, ai-gift-finder, ai-resume-roaster, ai-music-mood-radio, team-standup-bot, pet-adoption-board)varies
Files built per scenario37 (student-study-groups)6-25
Build time5-11 min5-7 min

Key findings:

  • User-story-focused prompts (no tech name-dropping) work โ€” plugin detects patterns from actual code
  • ai-sdk, shadcn, nextjs, vercel-functions are the most consistently detected skills
  • cron-jobs, routing-middleware need Claude to write specific file patterns to trigger
  • Lexical prompt inject (UserPromptSubmit) working โ€” skills injected before any files written
  • session-end-cleanup deletes claim dirs โ€” use poll history for final skill counts
  • Enterprise tier (vercel-labs) โ€” no sandbox time cap; builds ran 10+ minutes