Connectors
Linear
Mention an agent in a Linear issue comment.
Quickstart
Create an API key
Create a Linear user for the agent, or use your own, and a personal API key for it with read and create-comments access.
Add the route
npm add @rivet-dev/pi rivetkit hono @linear/sdk
import { pi } from "@rivet-dev/pi";
import { Hono } from "hono";
import { setup } from "rivetkit";
import { type LinearComment, linearRoutes, postReply } from "./linear";
const agent = pi({
model: "anthropic/claude-opus-5-5",
actions: {
receive: async (c, comment: LinearComment) => {
await c.schedule.after(0, "reply", comment);
},
reply: async (c, comment: LinearComment): Promise<void> => {
const self = c.client<typeof registry>().agent.getForId(c.actorId);
await self.prompt(comment.text);
await postReply(comment, await self.getLastAssistantText());
},
},
});
export const registry = setup({ use: { agent } });
const app = new Hono();
app.all("/api/rivet/*", (c) => registry.handler(c.req.raw));
app.route("/linear", linearRoutes);
export default app;
import { createHmac, timingSafeEqual } from "node:crypto";
import { LinearClient } from "@linear/sdk";
import { Hono } from "hono";
import { createClient } from "rivetkit/client";
import type { registry } from "./server";
export type LinearComment = { issueId: string; text: string };
const trigger = process.env.LINEAR_TRIGGER ?? "@agent";
const linear = new LinearClient({ apiKey: process.env.LINEAR_API_KEY });
const agentUserId = linear.viewer.then((viewer) => viewer.id);
const client = createClient<typeof registry>();
export const linearRoutes = new Hono();
linearRoutes.post("/webhook", async (c) => {
const body = await c.req.text();
if (!verifyLinearSignature(body, c.req.header("linear-signature") ?? "")) {
return c.text("invalid signature", 401);
}
const payload = JSON.parse(body);
if (Math.abs(Date.now() - payload.webhookTimestamp) > 60 * 1000) return c.text("stale webhook", 401);
const comment = payload.data;
if (
payload.type === "Comment" &&
payload.action === "create" &&
comment.issueId &&
comment.body.includes(trigger) &&
comment.userId !== (await agentUserId)
) {
const agent = client.agent.getOrCreate(["linear", comment.issueId]);
await agent.receive({ issueId: comment.issueId, text: comment.body });
}
return c.body(null, 200);
});
export async function postReply(comment: LinearComment, reply: string | undefined) {
await linear.createComment({ issueId: comment.issueId, body: reply || "I finished without a reply." });
}
function verifyLinearSignature(body: string, signature: string): boolean {
const secret = process.env.LINEAR_WEBHOOK_SECRET ?? "";
const expected = createHmac("sha256", secret).update(body).digest("hex");
return expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
Run the server with any runtime that serves a fetch handler, for example npx srvx --import tsx server.ts. See HTTP Server.
Set the environment
LINEAR_WEBHOOK_SECRET=lin_wh_...
LINEAR_API_KEY=lin_api_...
LINEAR_TRIGGER=@agent
ANTHROPIC_API_KEY=sk-ant-...
Add the webhook
In Linear’s Settings > Administration > API, which needs admin rights, create a webhook with the URL https://<your-worker>/linear/webhook and the Comments resource type. Copy its signing secret. Comment @agent on an issue.
How it works
- The route verifies the
Linear-SignatureHMAC against the raw body and rejects deliveries older than a minute. - Each Linear issue gets its own agent. Every comment with the trigger continues the same session.
- The route never answers the agent’s own comments, so replies do not loop.
- The route only hands the comment to the agent’s
receiveaction and returns.receiveschedulesreply, which prompts the agent and posts the answer as a comment with the Linear SDK. The schedule is saved with the agent, so the reply still runs if the request that delivered the message goes away.
Configure
| Variable | Purpose |
|---|---|
LINEAR_WEBHOOK_SECRET | Required. Verifies that deliveries come from Linear. |
LINEAR_API_KEY | Required. Posts replies as the agent’s Linear user. |
LINEAR_TRIGGER | Text that makes a comment start a reply. Defaults to @agent. |
ANTHROPIC_API_KEY | Required. The agent’s model key. See API Keys. |
Supported events
| Linear event | Webhook path | What the route does |
|---|---|---|
Comment, create | /linear/webhook | Hands the comment to the issue’s agent, which replies with a comment. |