Skip to main content
Private beta. This feature is only available to some organizations right now.Request access and we’ll turn webhooks on for your organization and send you the prerelease packages. Webhooks are open source, so they work self-hosted too. APIs may change before general availability.
Linear opens an agent session when someone @mentions your app in an issue or delegates (assigns) an issue to it. linear() from @trigger.dev/linear is a channel connector for those sessions: each one becomes a durable Trigger.dev session, the user’s prompt runs as a turn, and the agent’s work shows up in Linear as thoughts, actions, a plan, an approval card when it needs a decision, and a response when it is done.
The run() loop is the same one a browser chat or a Slack thread uses. Verification, the five second webhook acknowledgment, the first activity Linear expects within ten seconds, per-session state, dedup of Linear’s retries, replay and the transcript are handled for you.

Setup

1

Create the OAuth app

In Linear, create an OAuth application under Settings, API. Enable Webhooks on it and turn on the Agent session events category (Inbox notifications and Permission changes are useful too). Copy the webhook signing secret.
2

Install it as an agent

Authorize the app with actor=app in the authorization URL and request the app:assignable and app:mentionable scopes, so members can assign issues to it and @mention it. Installation needs a workspace admin. Keep the resulting access token; pass it as token.
3

Deploy and connect the endpoint

Declare the endpoint with webhookSource() from @trigger.dev/linear and pass it to linear({ endpoint }). Deploying registers the hosted endpoint. Paste the app’s webhook signing secret as the endpoint’s signing secret, and set the app’s webhook URL to the endpoint’s URL. The endpoint’s setup prompt walks an AI coding agent through these settings.
4

Try it

Open an issue and assign it to the app, or write a comment that @mentions it. Linear shows the agent’s first thought within a few seconds and then its work.

What the agent sees

On the first turn of a session the connector frames Linear’s payload for the model: who summoned it and on which issue, the workspace guidance rules for agents, Linear’s formatted issue context (title, description, labels, project, comments), and the comment that mentioned it. An assignment with no comment reads as “assigned ENG-412 to you”. Prior comments on the issue are seeded as transcript messages, so the agent can answer with the context a reader of the issue has. A follow-up prompt on the session is just the message. If the user hits Stop in Linear, see Stopping. Images and PDFs uploaded to Linear reach the model as files: screenshots in the prompt, in the comment that mentioned the agent, or in the issue description. The transcript keeps the upload link, and the streamText passed to run() downloads the file with the app’s token when the model reads it, at most ten per message. A file that can’t be downloaded reaches the model as a short note. A custom inbound keeps this by adding linearFileParts(markdown) to its message parts. Turn any of that off with context:

What Linear shows

Tool mirroring is on by default; pass progress: "off" to post only the response. Bare Linear URLs in a reply become mentions, so link issues and documents by URL.

The plan

Linear shows an agent’s plan as a checklist under the conversation. The model owns it: add linearPlanTool() to the agent’s tools and ask for a plan in the system prompt, and the model sets it when it starts and updates it as steps finish. The whole list is replaced on each call. On a turn that is not a Linear session the tool reports that nothing was updated, so run() needs no Linear-specific code.

Helpers

For manual control, chat.surface<LinearRunSurface>() returns the current turn’s helpers from anywhere inside the run, including a tool’s execute. The same object is handed to run() as channel.

Approvals and choices

A tool with no execute pauses the turn for a human decision, exactly as on any channel. On Linear the connector posts an elicitation card with Approve and Deny options. The card lays the tool input out as markdown: a list of proposed items becomes a numbered list with a bold title per item, a flat object becomes key and value bullets, and only shapes it cannot lay out fall back to JSON. For a card written for the tool, pass a renderer per tool name:
The user picks one and presses Continue; the choice shows in the session as “Approve createSubIssues [call_a]” (the tool and the call it applies to, so the decision always lands on the call whose input the card shows) and resumes the run with { approved: true | false }, and the decision is recorded as a thought. Typing a free-text reply instead dismisses the options and runs as a normal turn, so the model can take the reply into account (and ask again) with the approval still pending. interactions gives a tool its own choices instead of Approve and Deny. The card is a select of options, and the chosen option resolves the tool to { selected }:
The choice shows in the session as “High [choice:call_a]”, resumes the run with { selected: "High" }, and is recorded as a thought.

Stopping

Pressing Stop in Linear sends the session a prompt with the stop signal. While a turn is running, the connector aborts it through the agent’s stop signal (the same one the browser’s stop button uses): generation halts, the text written so far posts as the response with stopped: true, and the stop prompt itself is consumed. Pressed while the agent is idle, the prompt runs as a turn that tells the model to stop taking actions and summarize. A Stop pressed while a turn was running that arrives after the turn finished is consumed, so pressing Stop twice never adds a wind-down turn.

Prompts sent while the agent is working

A prompt sent while the agent is still working gets a thought as soon as it arrives (“Got it, I’ll take that into account.”), so the user knows it was picked up. It then waits and runs as the next turn. With pendingMessages on the agent, shouldInject can fold it into the work in progress at the next step instead; a prompt it declines still runs as its own turn. receivedThought changes the text, takes a function of the event for wording per prompt, and null posts nothing; since the prompt may be answered either way, keep the wording true for both. Stop and the approval card’s options are never acknowledged or folded in.

Answering into the session from elsewhere

The first Linear turn binds the session to the agent session (bound sessions). A chat.event routed to the same session key, a Sentry alert keyed on the issue identifier for example, can return chat.turn() from onAction and its answer posts into the Linear session as a response, with no Linear event involved. Linear reopens a completed session when new activity arrives.

Options

Linear expects the first activity within ten seconds of opening a session. The connector posts it at the top of the run, so on a cold start close to that limit the session can show as unresponsive for a moment; it recovers as soon as the thought lands. Linear retries a failed delivery after one minute, one hour and six hours, and the endpoint deduplicates those retries.