How is remote-bash built?
tell me how remote-bash is built
Now I have a comprehensive picture. Here's the full breakdown:
##How RemoteBash is Built
RemoteBash is a feature that lets you execute bash commands against any public GitHub repository (or npm package) in a sandboxed environment. It's exposed through three surfaces: the AI agent tool, the MCP server tool, and a REST API. Here's how it all fits together:
###Architecture Overview
┌─────────────────────────────────────────────────┐
│ Three Entry Points │
│ │
│ 1. Agent Tool (agent/tools/index.ts) │
│ 2. MCP Tool (app/mcp/tools/bash.ts) │
│ 3. REST API (app/api/remote-bash/) │
│ │
│ All call into the same core: │
│ │
│ lib/remote-bash/index.ts → remoteBash() │
│ ↓ │
│ agent/workspace.ts → startWorkspace() │
│ ↓ │
│ @vercel/sandbox (Sandbox) │
└─────────────────────────────────────────────────┘
###1. Core Library (lib/remote-bash/)
The core lives in four files:
####index.ts — Orchestrator
-
remoteBash(request)— The main function. It:- Validates inputs (repo, command required; timeout clamped to 1s–120s)
- Calls
resolveToGitContext()to figure out what to clone - Calls
startWorkspace()to get a sandbox with the repo - Calls
executeCommand()to run the user's command - Returns structured output (stdout, stderr, exitCode, resolvedRef, timing)
-
resolveToGitContext()— Resolves therepoparam (which can be a GitHub URL,owner/repo, or an npm package name) into aGitContext({owner, repo, ref}):- Uses
parseGitHubInput()fromlib/resolve-repo-input.tsto match GitHub URLs andowner/repopatterns via regex - If that fails, assumes it's an npm package and calls
resolveNpmPackage()fromlib/utils/match-package-with-repo.ts, which fetches the npm registry, extracts the GitHub repo URL, and finds thegitHead(exact commit SHA) or falls back to tag resolution - If a
versionis provided, it resolves it to a git tag viaresolveVersionToTag()
- Uses
####version-resolver.ts — Tag Resolution
- Fetches all tags from the GitHub API (paginated, up to 500)
- Tries multiple tag patterns in order:
v{version},{version},release-{version}, and monorepo patterns like{packageName}@{version} - Returns the matching tag name and SHA
####command.ts — Sandboxed Execution
executeCommand()runs the user's command inside the sandbox usingjust-bash(an npm package installed globally in the sandbox for read-only isolation):cd "{workspacePath}" && timeout {seconds} just-bash --root . -c '{command}'- First ensures
just-bashis installed (which just-bash || npm install -g just-bash) - Uses the UNIX
timeoutcommand to enforce the timeout (exit code 124 = timeout) - Collects stdout/stderr from the sandbox's streaming logs
####errors.ts — Typed Error Hierarchy
A clean error hierarchy with HTTP status codes:
RemoteBashError(base) →RepoNotFoundError(404),VersionNotFoundError(404),TagNotFoundError(404),CommandTimeoutError(408),OutputTruncatedError(200),RateLimitError(429),SandboxError(500)
###2. Sandbox & Workspace (agent/workspace.ts)
This is the infrastructure layer powered by @vercel/sandbox:
-
startWorkspace()is the key function. It returns aLazyWorkspace— the setup happens in the background while the caller gets back immediately with ashaand arunCommandwrapper. -
Sandbox pooling via Redis: Sandboxes are shared and reused across requests for the same
owner/repo. Uses Redis withgetOrLockSandbox()for distributed locking — only one process creates a sandbox, others wait with exponential backoff. -
Shallow cloning: Resolves the git ref to a SHA (via GitHub API) before entering the sandbox, then does a
git init→git fetch --depth 1 origin {SHA}→git checkout FETCH_HEADinside the sandbox. This means only the exact commit is fetched. -
Lazy readiness pattern: The background setup writes a ready file (
/tmp/.workspace-ready-{shortSha}). TherunCommandwrapper polls for this file inside bash before executing:while [ ! -f "{readyFile}" ]; do sleep 0.1; done; exec "$@"This means there are no Node.js round trips for the wait — it all happens inside the sandbox.
-
Ripgrep installation: Also installs
ripgrepin the background in parallel with the clone (downloads the musl binary from GitHub releases to~/.local/bin). -
Sandboxes have a 10-minute timeout and get extended on reuse. They use 2 vCPUs.
###3. Three Entry Points
####Agent Tool (agent/tools/index.ts, line 605)
When the AI agent uses RemoteBash, it actually shells out to npx -y remote-bash inside the current workspace sandbox. This is a different path than the REST API — it runs the remote-bash CLI as a subprocess.
####MCP Tool (app/mcp/tools/bash.ts)
The MCP server exposes it as a bash tool. It:
- Authenticates via MCP OAuth session
- Rate-limits per user (
checkMessageRateLimit) - Checks billing/credits via Autumn (Pro users get 1500/month, free get 5/day)
- Calls
remoteBash()directly from the core library - Returns both
content(text for display) andstructuredContent(typed result)
####REST API (app/api/remote-bash/route.ts)
Exposes both GET and POST endpoints:
- Validates with Zod schema
- Rate-limits by IP (not user) — 10/minute, 100/day — via Upstash Redis sliding windows
- Calls
remoteBash()directly - Returns JSON with proper HTTP status codes
###4. Rate Limiting (lib/rate-limit.ts)
Uses Upstash Ratelimit with Redis-backed sliding windows:
- REST API: 10 requests/minute, 100/day per IP address
- MCP: Uses the per-user message rate limit (20/min, 200/hour, 400/day)
###5. Supporting Infrastructure
lib/github-fetch.ts— Wrapsfetchfor GitHub API calls with automatic auth (GITHUB_TOKEN), retry with exponential backoff (3 attempts), and fallback to unauthenticated requests on 403.lib/utils/match-package-with-repo.ts— npm registry integration: fetches package metadata, extracts and normalizes the repository URL (handlesgit+https://,git://,ssh://, GitHub shorthand), and resolves versions.
###Key Design Decisions
- Read-only by default — Commands run through
just-bash --root .which blocks writes - Lazy workspace — Clone starts in background; commands wait via bash polling (no Node round trips)
- Sandbox reuse — Redis-backed pooling means subsequent requests to the same repo reuse the same sandbox
- Shallow clones — Only fetches the single commit needed (
git fetch --depth 1) - Triple input format — GitHub URLs,
owner/repo, and npm package names all work, with smart version-to-tag resolution - Timeout enforcement — Both at the UNIX level (
timeoutcommand) and at the application level (clamped to 120s max)
END OF POST