Skip to content

Jira and Linear

Semaloop has no native Jira or Linear integration, but you can file failed test runs as tickets in either one with a webhook or with the MCP server. This page covers both routes.

WebhookMCP server
When a ticket is createdAutomatically, as each run failsWhen you ask your coding agent
What you runA small handler, or an automation toolA coding agent connected to two MCP servers
Best forEvery failure, with no one in the loopRecurring issues, combined with context you already have

Semaloop sends a signed test_run.status_changed.v1 event when a run reaches success, failure or error. See webhooks for the payload.

  1. Deploy an endpoint that can receive a POST request. It can be a small serverless function or a step in an automation tool.
  2. Go to Settings → Webhooks → Set up webhooks and add the endpoint.
  3. Add a transformation to the endpoint so only failures are delivered. The webhooks page has the code.
  4. In the endpoint, verify the signature, then create the ticket from data.failureReason and data.url.
  5. Deduplicate on the event’s id. It is stable per run and status, and a run that is retried automatically only sends an event for its final attempt.

This handler verifies the signature with the Svix library and creates a Jira issue. Change the site, project key and issue type to match your account.

import { Webhook } from "svix";
export default {
async fetch(request, env) {
const body = await request.text();
const event = new Webhook(env.SEMALOOP_WEBHOOK_SECRET).verify(body, {
"svix-id": request.headers.get("svix-id"),
"svix-timestamp": request.headers.get("svix-timestamp"),
"svix-signature": request.headers.get("svix-signature"),
});
const { failureReason, url } = event.data;
await fetch(`https://${env.JIRA_SITE}.atlassian.net/rest/api/2/issue`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Basic ${btoa(`${env.JIRA_EMAIL}:${env.JIRA_API_TOKEN}`)}`,
},
body: JSON.stringify({
fields: {
project: { key: "APP" },
issuetype: { name: "Bug" },
summary: `Semaloop test failed: ${failureReason}`,
description: `${failureReason}\n\nRun: ${url}`,
},
}),
});
return new Response("ok");
},
};

For Linear, replace the fetch call with a GraphQL mutation against https://api.linear.app/graphql, sending your Linear API key in the Authorization header:

await fetch("https://api.linear.app/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: env.LINEAR_API_KEY,
},
body: JSON.stringify({
query: `mutation ($input: IssueCreateInput!) {
issueCreate(input: $input) { success issue { identifier url } }
}`,
variables: {
input: {
teamId: env.LINEAR_TEAM_ID,
title: `Semaloop test failed: ${failureReason}`,
description: `${failureReason}\n\nRun: ${url}`,
},
},
}),
});

These snippets show the shape of each call. Check your tracker’s API documentation for the authentication, field names and required fields your account uses.

Route 2: a coding agent connected to both servers

Section titled “Route 2: a coding agent connected to both servers”

Connect your coding agent to the Semaloop MCP server and to your tracker’s own MCP server, then ask it to do the filing. It can read your apps’ runs and recurring issues from Semaloop and create tickets in Jira or Linear, combined with whatever else you have to hand, such as support tickets or meeting notes. For example:

Use Semaloop to list the failed runs on the latest build of our iOS app and the open issues. For each one that isn’t already in Linear, create an issue with the failure reason, a link to the run, and what the video and logs show.

Reading runs and issues over MCP is open to every member of your organisation. The agent only needs write access in your tracker.