A complete, working implementation of this recipe — downloadable as a zip or browsable file by file. Generated by our build pipeline; tested with full coverage before publishing.
In this tutorial, you’ll build a serverless incident-analysis pipeline that listens for Jira Service Management ticket creation events, launches an isolated E2B sandbox to run a Python script that detects incident trends, validates the output with a structured repair engine, and posts the analysis back as a Jira comment. You’ll wire in a budget engine to cap compute costs per user and a circuit breaker to prevent runaway scripts.
This recipe is built with Next.js 16 (App Router), TypeScript, and four vendored REAA packages. By the end, you’ll have a production-shaped API endpoint ready to be configured as a Jira webhook target.
Prerequisites
Node.js 22+ with pnpm installed (npm install -g pnpm)
An E2B account — sign up at e2b.dev and create an API key
A Jira Cloud instance with an API token — generate one from https://id.atlassian.com/manage-profile/security/api-tokens
A Langfuse account (optional) for execution tracing — sign up at langfuse.com
Basic familiarity with Next.js App Router, TypeScript, and REST APIs
Step 1: Scaffold the Next.js project and install dependencies
Create a new Next.js 16 project with TypeScript and the App Router:
cd code-sandbox-for-jira-service-management-smb-it-teams
Add all the dependencies at their exact pinned versions. This includes Next.js, React, and React DOM to override whatever version the scaffold produced:
Copy the file to .env and fill in your real credentials:
terminal
cp .env.example .env
Expected output:JIRA_WEBHOOK_SECRET is optional — when set, incoming webhook payloads are HMAC-verified. The budget and circuit-breaker env vars control default behavior and can be overridden at runtime.
Step 3: Define TypeScript types
Create src/types.ts with all the interfaces your services will share:
Expected output: Three schemas — jiraTicketSchema for Jira payload validation, scriptResultSchema for sandbox results, and analysisResultSchema defining the shape the structured repair engine will enforce before comments are posted to Jira.
Step 5: Build the budget control service
Create src/services/budget.ts. This service wraps @reaatech/agent-budget-engine’s BudgetController to track compute spend per user email. The @reaatech/agent-budget-spend-tracker package is not vendored, so you’ll provide an in-memory spend store that satisfies the spendTracker constructor argument:
Expected output: Four exported functions. createBudgetController() sets up a wildcard budget at the default limit with soft/hard cap policies. checkExecutionCost() runs a pre-flight check before the sandbox starts. recordExecutionCost() logs spend after execution completes. getScopeState() returns current budget status for a user.
Step 6: Build the circuit breaker service
Create src/services/circuit-breaker.ts. This service wraps @reaatech/circuit-breaker-core to prevent runaway sandbox executions after repeated failures:
Expected output:createSandboxCircuitBreaker() configures a breaker with 3-failure threshold and 5-minute gradual recovery. executeWithBreaker() delegates to breaker.execute() with an optional fallback. When the circuit is OPEN and no fallback is provided, CircuitOpenError is thrown.
Step 7: Build the structured repair service
Create src/validation/repair.ts. This service wraps @reaatech/structured-repair-core to validate and repair malformed sandbox output before it’s posted to Jira:
ts
import { repair, repairOutput, isValid, analyzeInput } from "@reaatech/structured-repair-core";import type { RepairResult } from "@reaatech/structured-repair-core";import type { z } from "zod";export async function validateAndRepair<T>( raw: string, schema: z.ZodType<T>,): Promise<T> { return repair(schema, raw);}export function validateAndRepairDetailed<T>( raw: string, schema: z.ZodType<T>,): RepairResult<z.infer<typeof schema>> { return repairOutput({ schema, input: raw, debug: false });}export function checkIsValid<T>( raw: string, schema: z.ZodType<T>,): boolean { return isValid(schema, raw);}export function diagnoseOutput(raw: string) { return analyzeInput(raw);}
Expected output: Four functions. validateAndRepair() runs the full default strategy pipeline (strip fences, fix JSON syntax, coerce types, fuzzy-match keys, remove extra fields) and returns typed data or throws UnrepairableError. validateAndRepairDetailed() returns a full RepairResult with per-step metadata.
Step 8: Create the E2B sandbox runner
Create src/sandbox/runner.ts. This module provisions an isolated E2B sandbox, pre-installs Python analytics libraries, executes the user’s script, and always destroys the sandbox in a finally block:
Expected output:runScriptAnalyzer() provisions a sandbox via Sandbox.create() from @e2b/code-interpreter, pre-installs packages via sandbox.commands.run() from the base e2b SDK, runs the script via sandbox.runCode(), and always calls sandbox.kill() in the finally block — even on error.
Step 9: Build the Jira API client
Create src/api/jira.ts. This module implements the JiraClient interface against the Jira Cloud REST API v3 with Basic Auth:
Expected output: Three methods on the returned JiraClient. getTicket() fetches a single issue. postComment() posts analysis in Atlassian Document Format. getTicketsInProject() searches by project key. All use Buffer.from(...).toString("base64") for Basic Auth (Node runtime) and throw structured errors on non-2xx responses.
Step 10: Build the webhook handler
Create src/webhook/jira.ts. This module parses incoming Jira webhook payloads, verifies HMAC signatures, builds analysis Python scripts, and provides helper predicates:
ts
import crypto from "node:crypto";import type { JiraWebhookPayload } from "../types.js";import type { ScriptDefinition } from "../types.js";function assertString(value: unknown, label: string): string { if (typeof value !== "string") { throw new TypeError(`Missing or invalid ${label}`); } return value;}function extractString(obj: Record<string, unknown
Expected output:parseWebhookPayload() safely extracts typed data from the Jira payload with TypeError on missing fields. verifyWebhookSignature() validates the HMAC-SHA256 signature. buildAnalysisScript() generates a Python script that receives ticket data via sys.argv, runs keyword frequency analysis, and prints a JSON AnalysisResult to stdout. The void ticket; expression uses the caller’s param to satisfy noUnusedParameters linting.
Step 11: Add observability with Langfuse
Create src/services/tracing.ts with a factory that returns a real Langfuse client when keys are configured and a no-op stub otherwise:
Expected output:createTracer() returns a no-op tracer when Langfuse is not configured, so the pipeline works without it. traceExecution() is safe to call on either the real or stub tracer.
Step 12: Wire up the main orchestration
Create src/index.ts as the barrel export and pipeline orchestrator. This file re-exports every public API and implements handleWebhook() — the core pipeline that ties everything together:
ts
export { createBudgetController, checkExecutionCost, recordExecutionCost, getScopeState } from "./services/budget.js";export { createSandboxCircuitBreaker, executeWithBreaker } from "./services/circuit-breaker.js";export { CircuitOpenError } from "@reaatech/circuit-breaker-core";export { validateAndRepair, validateAndRepairDetailed, checkIsValid, diagnoseOutput } from "./validation/repair.js";export { runScriptAnalyzer } from "./sandbox/runner.js";export { createJiraClient } from "./api/jira.js";export { parseWebhookPayload, verifyWebhookSignature, extractTicket, buildAnalysisScript, isTicketCreationEvent } from "./webhook/jira.js";export { createTracer, traceExecution } from "./services/tracing.js";import type { AnalysisResult } from "./types.js";import type { WebhookConfig }
Expected output:handleWebhook() is the full pipeline: parse payload → check budget → build script → run sandbox via circuit breaker → validate output with structured repair → post comment to Jira → trace execution. createRequestId() generates unique request IDs for spend tracking. Every error is caught and returned as { ok: false, error } — never an uncaught exception.
Step 13: Set up the Next.js API route and instrumentation
Create app/api/jira-webhook/route.ts — the webhook endpoint that Jira will POST to:
Expected output: The API route uses NextRequest/NextResponse.json() (no bare Request/new Response()). It reads the raw body for signature verification, parses JSON, loads config from env vars, delegates to handleWebhook(), and maps error types to HTTP status codes (402 for budget, 503 for circuit open, 422 for validation failure, 400 for generic errors). The instrumentation hook is enabled with experimental.instrumentationHook: true — without this flag, register() in instrumentation.ts would be dead code.
Step 14: Run the tests
The recipe comes with tests for every module. Here’s the sandbox runner test at tests/sandbox/runner.test.ts:
Expected output: All 71 tests pass with zero failures. Coverage metrics (lines, branches, functions, statements) are all at 90% or above across src/**/*.ts and app/**/route.ts.
Run the type checker:
terminal
pnpm typecheck
Expected output: Zero type errors. Run pnpm lint for zero ESLint errors.
Next steps
Deploy to production — containerize with Docker and deploy to a cloud runtime that supports Next.js (Vercel, Railway, or a self-hosted Node.js server). Configure your Jira project’s webhooks to POST to https://<your-domain>/api/jira-webhook on jira:issue_created events.
Extend the analysis script — replace the keyword-frequency Python script with a real ML model (e.g., a fine-tuned classifier for incident severity prediction) or integrate with OpenAI to generate natural-language summaries from ticket descriptions.
Add a dashboard — build a UI page under app/dashboard/ that displays the getScopeState() output, recent sandbox execution times, and a circuit breaker status indicator so your IT team can monitor system health at a glance.