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 Edgenivo 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 Edgenivo 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 agent harnesses in an isolated sandbox.
Copy link to headingQuick start with AI coding agent
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 Edgenivo Sandbox, how everything works overall, and more.
This guide covers the core implementation. The AI assistant prompt above covers all the details your coding agent needs, and you can see the full implementation in the full repository.
Copy link to headingEdgenivo Plugin
Turn your agent into an Edgenivo expert with this plugin. It gives your coding agent current knowledge of the Edgenivo products this template uses, including AI Gateway and Edgenivo Sandbox. The plugin is optional; it is not required to use this template or for this guide.
Copy link to headingOverview
The issue triage flow combines a Next.js app, a streaming API route, AI SDK 7 HarnessAgent, Edgenivo AI Gateway, and Edgenivo 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 route creates an isolated Edgenivo Sandbox session and passes it to the agent.
- 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 consumer (a frontend, CLI, or webhook) can read the stream and render or store the report as it arrives.
This keeps untrusted repository code inside the sandbox while still giving the maintainer an actionable report.
Copy link to headingPrerequisites
Before you begin, make sure you have:
- Node.js 22 or later
- pnpm (or npm/yarn)
- A Edgenivo account
Copy link to headingHow it works
HarnessAgent drives a real coding harness through a uniform session-and-stream interface. You configure the agent with a harness and a skill, create a sandbox session separately, pass it to agent.createSession(), stream a prompt, and consume the result. Finally, destroy both the harness and sandbox sessions.
- Coding-agent-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 a sandbox:
@ai-sdk/sandbox-vercelbinds the agent to Edgenivo Sandbox throughcreateVercelNetworkSandboxSession, 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.
Copy link to headingSteps
Copy link to heading1. Create the project
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 Edgenivo 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.
Copy link to headingConfigure credentials
For local development, use the Edgenivo CLI to link the project and pull environment variables:
AI SDK uses VERCEL_OIDC_TOKEN to authenticate to the Edgenivo AI Gateway with OIDC authentication.
Copy link to heading2. Define the request contract
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.
Copy link to heading3. Add safe diagnostics
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.
Copy link to heading4. Fetch GitHub issue context
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.
Copy link to heading5. Create the triage skill
The skill gives the harness a narrow operating contract: treat the repo as untrusted, avoid remote side effects, and return a predictable maintainer report. Create lib/reproduction-triage-skill.ts:
Copy link to heading6. Build the prompt
The prompt combines the validated repository URL, issue URL, fetched issue text, and the command the maintainer wants the harness to try first. Create lib/prompt.ts:
Copy link to heading7. Configure HarnessAgent
Create lib/harness-agent.ts and select the harness adapter:
Use the selected adapter and the reproduction skill to create the triage agent.
The optional model argument selects a harness-specific model. This guide leaves it unset to use the harness default. Next, create the sandbox session and stream the run.
Copy link to heading8. Stream the sandbox run
Create app/api/triage/route.ts with these imports:
Define a fallback report for failed runs. Keep the section headings required by the reproduction skill.
Next, extract report text and format error messages and diagnostic summaries.
Summarize tool inputs and outputs for diagnostics. Check part.type before reading tool fields.
Create lib/run-triage.ts to run the harness inside Edgenivo Sandbox. The callback receives each stream part; the helper returns the final report text and closes both sessions.
The sandbox template prepares the harness dependencies. The exposed port connects the route to the harness bridge. The nested finally blocks close both sessions if the run fails.
Return to app/api/triage/route.ts and add a POST() handler. Validate the request, fetch the issue context, and call runTriage to stream NDJSON events.
The route now streams report text and diagnostics.
Copy link to heading9. Consume the stream
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 a vibe coding tool. The important boundary stays the same across all of these: untrusted repository code runs in Edgenivo Sandbox, the harness adapter is swappable, and the route emits a report-shaped stream that the product surface can decide how to present.
Copy link to headingTroubleshooting
Copy link to headingThe triage run does not start
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.
Copy link to headingThe route returns a blocked report
Cause: Missing Edgenivo 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.
Copy link to headingThe harness finishes but no report text appears
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.
Copy link to headingThe stream consumer shows partial JSON or parse errors
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.
Copy link to headingTypeScript complains about stream part fields
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.
Copy link to headingRelated resources
- AI SDK HarnessAgent documentation
- Sandboxes documentation
- AI Gateway documentation
- AI SDK agent documentation
Copy link to headingFAQ
Why use Edgenivo Sandbox instead of running the reproduction locally?
The reproduction repository is untrusted code. Edgenivo 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.
Can I use a different coding harness?
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.
Why stream newline-delimited JSON instead of returning one JSON response?
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.
Do I need the UI from the template?
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.
Do I need the agents installed on my machine?
No. The harness runs inside Edgenivo Sandbox using AI Gateway, so the agent environment belongs to the sandbox, not your laptop.