Build a GitHub issue triage agent that reproduces the issue, investigates it in a safe environment, and generates a report for the maintainer to speed up their work. It uses AI SDK's HarnessAgent to drive a real coding harness (e.g., Claude Code, Codex, Pi, or OpenCode) inside a Vercel Sandbox, through a single uniform agent interface, with AI Gateway routing every model call. AI Gateway makes updating to a different harness a one-line change, and Vercel Sandbox keeps the untrusted code off the maintainer's machine.
In this guide, you’ll build a Next.js app that uses the HarnessAgent primitive to triage GitHub issues from their public issue URL, validate the steps to reproduce the issue, and return a structured report for the maintainer.
Deploy the template now, or read on for a deeper look at how it all works.
GitHub issue triage agent with HarnessAgent
Securely triage GitHub issues using coding agents harness in an isolated sandbox.
I want to build a sandboxed issue triage agent using the sandboxed issue triage agent template. Read the setup instructions at https://agent-resources.dev/sandboxed-issue-triage-agent-template.md and follow them. They will cover deploying the template, building with HarnessAgent and Vercel Sandbox, how everything works overall, and more.
Turn your agent into a Vercel expert with this plugin. It gives your coding agent current knowledge of the Vercel products this template uses, including AI Gateway and Vercel Sandbox. The plugin is optional; it is not required to use this template or for this guide.
The issue triage flow combines a Next.js app, a streaming API route, AI SDK 7 HarnessAgent, Vercel AI Gateway, and Vercel Sandbox. A run follows this path:
- A maintainer enters a public GitHub issue URL.
- The UI sends the issue URL, failing command, and harness adapter to
/api/triage. - The API route validates the issue URL and fetches issue context from GitHub.
- The route creates a
HarnessAgentwith the selected harness adapter. - The agent creates an isolated Vercel Sandbox session.
- The prompt asks the harness to inspect the repository and run the failing command.
- The API route streams debug events, tool activity, and report text as newline-delimited JSON.
- Any frontend, CLI, or webhook consumer can read the stream and render or store the report.
This keeps untrusted repository code inside the sandbox while still giving the maintainer an actionable report.
Before you begin, make sure you have:
- Node.js 22 or later
- pnpm (or npm/yarn)
- A Vercel account
HarnessAgent drives a real coding harness inside a sandbox provider, behind a uniform session-and-stream interface. You do not script the harness’ I/O or manage the sandbox lifecycle by hand. You construct the agent with a harness, a sandbox, and a skill, open a session, stream a prompt, and consume the result.
- Coding-agents-specific harness adapters:
@ai-sdk/harness-claude-codeand@ai-sdk/harness-codexeach wrap a real agentic coding tool, Claude Code and OpenAI's Codex. Each adapter is constructed with the same signature, so the only difference between them at the call site is which factory you call. - Isolation with sandbox:
@ai-sdk/sandbox-vercelbinds the agent to Vercel Sandbox throughcreateVercelSandbox, so the harness and any code it executes run inside an isolated microVM, not on the host. This makes investigating untrusted code defensible. - Access coding agents via AI Gateway: The adapters take a
gatewayauth object rather than a provider-specific API key, so model access authenticates through one credential path. There are no Anthropic or OpenAI keys in the application.
Create a new Next.js app with create-next-app. The --yes flag applies the recommended defaults, including TypeScript, ESLint, App Router, and the @/* import alias.
Then add the AI SDK harness packages and the Vercel Sandbox adapter used by the route.
Next.js should keep the harness and sandbox packages external to the server bundle. Add the external package list to next.config.ts.
For local development, use the Vercel CLI to link the project and pull environment variables:
AI SDK uses VERCEL_OIDC_TOKEN to authenticate with the Vercel AI Gateway with OIDC authentication.
The route accepts a GitHub issue URL, an optional failing command, and the harness adapter. Start with the shared types in lib/types.ts.
The validated request includes the parsed GitHub owner, repository, issue number, and repository URL. The route uses this parsed shape for all downstream work.
The validation result is explicit so the route can return a 400 response without throwing when user input is invalid.
Validate the request body and apply defaults. Missing failing commands default to npm test, and unknown harness values default to Claude Code.
Parse the issue URL defensively. This lets bad URLs fail with a useful validation message instead of an unhandled route error.
Extract the GitHub path parts and make sure the URL points to an issue, not an arbitrary GitHub page.
The template streams debug events to the UI, but it also redacts sensitive metadata before logging. Start lib/debug.ts with the metadata type and redaction pattern.
Use a recursive helper so nested metadata objects and arrays are redacted consistently.
Expose a sanitizer for route events and UI stream metadata.
Error metadata is normalized so both server logs and stream consumer diagnostics have the same shape. Also, add the createRunId() helper function:
Finally, wrap console logging so all triage logs share the same prefix and redaction behavior.
The agent needs issue context before it starts the sandbox run. Define the GitHub response shapes in lib/github.ts.
Fetch JSON from GitHub with explicit headers and throw a clear error when the API response is not successful.
Now create the issue-context helper. It starts with the issue endpoint derived from validated input. Fetch up to ten comments when the issue has comments. This keeps the prompt useful while staying bounded.
The skill gives the harness a narrow operating contract: treat the repo as untrusted, avoid remote side effects, and return a predictable maintainer report.
The prompt combines the validated repository URL, issue URL, fetched issue text, and the command the maintainer wants the harness to try first.
The agent setup uses Vercel AI Gateway for model access and Vercel Sandbox for isolated execution.
Resolve the selected harness adapter. The rest of the app does not need to know whether the run uses Claude Code or Codex.
Create the triage agent for each run. The sandbox option is the core safety boundary: unknown repository code runs in Vercel Sandbox instead of on the maintainer machine.
The API route bridges the caller, GitHub, the harness, and the sandbox. It returns newline-delimited JSON so any consumer can render or store report text and diagnostics as they arrive.
Normalize stream parts from different adapters so the route can handle both text and delta fields.
Small helpers keep adapter stream handling and metadata summaries consistent.
Summarize tool inputs and outputs before sending them to diagnostics. Narrow on part.type before reading tool fields so TypeScript enforces the actual stream part shape.
The POST() handler is intentionally one orchestration function in the template. It validates input, creates an NDJSON stream, fetches GitHub context, creates a sandbox-backed harness session, forwards result.stream, recovers final text when needed, streams blocked reports for failures, destroys the session, and returns the response with application/x-ndjson.
The route expects a JSON body with the issue URL, optional failing command, and optional harness adapter. A caller can also pass x-triage-run-id to correlate caller logs with server logs.
The response is newline-delimited JSON. report events contain text to append to the final maintainer report.
debug, error, and activity events contain progress and diagnostics that a frontend, CLI, or background worker can render separately.
You now have a GitHub issue triage agent ready to investigate issues. The complete template adds a simple Next.js UI on top of this route: a form for the GitHub issue URL, failing command, and harness adapter; a report panel that appends report events; and a diagnostics panel that renders debug, error, and activity events. That UI is not required for the sandboxed-agent pattern. It is just one consumer of the newline-delimited JSON stream.
To get started, clone the repository, configure credentials, and run the local app:
Open http://localhost:3000 and submit:
A successful run streams a report with these sections:

This project generalizes beyond issue triaging. The same primitives apply to more sophisticated use cases like a coding interview platform or vibe coding tool. The important boundary stays the same across all of these: untrusted repository code runs in Vercel Sandbox, the harness adapter is swappable, and the route emits a report-shaped stream that the product surface can decide how to present.
Cause: The issue URL is empty, malformed, points to a pull request, or points to another GitHub page. The route only accepts public GitHub issue URLs in the form https://github.com/{owner}/{repo}/issues/{number}.
Fix: Send a public issue URL. If you want to support pull requests later, add a separate validation branch and prompt contract for PR context.
Cause: Missing Vercel Sandbox credentials, missing AI Gateway credentials, an unavailable GitHub issue, or a harness adapter startup failure. The template intentionally converts startup and adapter failures into report-shaped output so the caller still receives a useful response.
Fix: Run vercel link, pull environment variables with vercel env pull .env.local, and confirm VERCEL_OIDC_TOKEN is available locally.
Cause: Some adapters may finish without emitting text-delta events, or the run may end before the model produces report text.
Fix: Keep the final-text recovery branch in the POST() handler. If result.text is also empty, return a blocked report that points the caller to diagnostics. Use the streamed debug events to inspect the last adapter activity.
Cause: NDJSON events can arrive split across network chunks. A single reader.read() call is not guaranteed to contain a complete JSON line.
Fix: Buffer text until a newline is available, parse one line at a time, and ignore empty lines. Treat report events as append-only text and render debug, error, and activity events separately.
Cause: The raw harness stream type and the translated AI SDK stream type are not the same surface. Redeclaring a local union can drift as the harness adapters evolve.
Fix: Use TextStreamPart<ToolSet> from ai and narrow on part.type before reading fields like text, toolName, toolCallId, input, or output.
- AI SDK HarnessAgent documentation
- Sandboxes documentation
- AI Gateway documentation
- AI SDK agent documentation
The reproduction repository is untrusted code. Vercel Sandbox gives the harness an isolated environment for installing dependencies, running commands, and inspecting files without asking a maintainer to run unknown code on their laptop.
Yes. The route is built around HarnessAgent, so the harness adapter is swappable. This guide uses Claude Code and Codex as examples, but the same pattern can support Pi, OpenCode, and other adapters as they become available.
Reproduction triage can take time. NDJSON lets the route stream tool activity, debug events, errors, and report text as they happen, while still ending with a report that a maintainer can copy or store.
No. The UI is one consumer of the stream. You can also consume the same route from a CLI, GitHub App, Slack bot, Linear workflow, dashboard, or background job.
No. The harness runs inside Vercel Sandbox using AI Gateway, so the agent environment belongs to the sandbox, not your laptop.