---
title: Linear Reference
description: Linear channel options, credentials, webhook verification, Agent Activity delivery, human-in-the-loop, custom hooks, proactive sessions, and API helpers.
url: "https://eve.dev/docs/channels/linear/reference"
docs_index: /llms.txt
type: reference
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

This page documents every part of `linearChannel` from `eve/channels/linear`. For setup and common tasks, start with [Linear](/docs/channels/linear).

The Linear channel lets people delegate issues to your agent, or mention it, in Linear. It lives in `agent/channels/linear.ts` and uses Linear Agent Sessions. The agent shows its progress and answers as native Agent Activities: `thought`, `action`, `elicitation`, `response`, and `error`.

[Vercel Connect](/docs/guides/auth-and-route-protection) can manage the Linear app, its token, and webhook checks, so you hold no API key or webhook secret. See [Channels](/docs/channels) for what all channels share.

## Guided Connect setup

Run the registry setup from the agent directory:

```bash
eve add channel/linear
```

To let the agent search and update Linear through MCP, install the separate Linear connection:

```bash
eve add connection/linear
```

The channel command signs you in to Vercel, links a project, and reuses or creates an app-scoped Linear Connect client that sends webhooks to `/eve/v1/linear`. It then installs `@vercel/connect` and writes the channel file:

```ts title="agent/channels/linear.ts"
import { connectLinearCredentials } from "@vercel/connect/eve";
import { linearChannel } from "eve/channels/linear";

export default linearChannel({
  credentials: connectLinearCredentials("linear/my-agent"),
});
```

Connect creates the Linear app with the `app:assignable` and `app:mentionable` scopes that Agent Sessions require. Connect forwards the app's webhooks, and eve checks their Vercel OIDC signature. Connect also handles token rotation and multiple workspaces. After you deploy, open the Linear app in the Connect dashboard and install it in your workspace. Then delegate an issue or mention the agent.

### Bring your own Linear app

To run a Linear OAuth app you manage yourself, pass its credentials directly:

```ts title="agent/channels/linear.ts"
import { linearChannel } from "eve/channels/linear";

export default linearChannel({
  credentials: {
    accessToken: process.env.LINEAR_AGENT_ACCESS_TOKEN,
    webhookSecret: process.env.LINEAR_WEBHOOK_SECRET,
  },
});
```

```bash
LINEAR_AGENT_ACCESS_TOKEN=lin_api_... # posts Agent Activities and creates proactive sessions
LINEAR_WEBHOOK_SECRET=...             # verifies Linear-Signature
```

Omit `credentials` to read the access token from `LINEAR_AGENT_ACCESS_TOKEN`, `LINEAR_ACCESS_TOKEN`, `LINEAR_API_KEY`, or `LINEAR_API_TOKEN`. The webhook secret then comes from `LINEAR_WEBHOOK_SECRET`. Both fields also accept a function.

Create the Linear OAuth app, enable Agent Session events, and point the webhook URL at:

```text
https://<deployment>/eve/v1/linear
```

Configure the OAuth authorize URL with `actor=app` and grant the scopes that let the app appear as an agent, including `app:assignable` and `app:mentionable`. Subscribe to the `AgentSessionEvent` webhook category. Linear then sends `created` when someone delegates to or mentions the agent, and `prompted` when the user replies.

eve checks the `Linear-Signature` HMAC over the raw body. It rejects a `webhookTimestamp` older than 60 seconds, as Linear recommends. Raise `maxSkewMs` (milliseconds) only to accept delayed retries, and keep it as small as your retry policy allows.

If a trusted gateway verifies Linear webhooks first, pass `credentials.webhookVerifier` instead of a webhook secret. `maxSkewMs` doesn't apply to a verifier, so the verifier must check timestamps itself.

## Options

`linearChannel(config)` takes these options. All are optional.

| Option           | Default                                                 | Description                                                                                     |
| ---------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `credentials`    | `LINEAR_AGENT_ACCESS_TOKEN` and `LINEAR_WEBHOOK_SECRET` | From `connectLinearCredentials(...)`, or `{ accessToken, webhookSecret, webhookVerifier }`.     |
| `route`          | `/eve/v1/linear`                                        | Webhook path.                                                                                   |
| `maxSkewMs`      | `60000`                                                 | Allowed webhook timestamp age for the built-in verifier. A custom `webhookVerifier` ignores it. |
| `api`            | `https://api.linear.app/graphql` and the global `fetch` | `{ apiBaseUrl, fetch }` for GraphQL calls.                                                      |
| `turnPolicy`     | `"steer"`                                               | `"steer"` or `"queue"` for prompts that arrive during a turn.                                   |
| `onAgentSession` | Dispatch `created` and `prompted` events                | `(ctx, event)` hook. See [Custom hooks](#custom-hooks).                                         |
| `onDataWebhook`  | None                                                    | `(event)` handler for other Linear data webhooks delivered to the same route.                   |
| `events`         | eve's default handlers                                  | Per-event handler overrides. See [Custom hooks](#custom-hooks).                                 |

The channel mounts `POST /eve/v1/linear`.

## Dispatch

By default, `created` and `prompted` Agent Session events start a turn. The model sees the agent session, issue, comment, and organization ids, plus `app_user_id` when known. Each Linear Agent Session maps to one eve session, keyed `agent-session:<id>`.

## Delivery

| Turn event              | Agent Activity                                     |
| ----------------------- | -------------------------------------------------- |
| Turn starts             | Ephemeral `thought`                                |
| Text before a tool call | Ephemeral `thought`, from its first non-empty line |
| Tool call               | Ephemeral `action`                                 |
| Final assistant text    | `response`, not ephemeral                          |
| Failure                 | `error`                                            |

## Human-in-the-loop (HITL)

Approvals and questions post as `elicitation` activities. The user's reply in the Agent Session answers the open one, and the turn continues.

## Connection authorization

When a connection needs the user to sign in, the channel posts Linear's native `auth` elicitation with the provider's sign-in URL. It goes to the user who started the session. A device flow without a URL posts its instructions and user code as a plain elicitation. When sign-in ends, the channel posts a `thought` with the result, and a successful sign-in continues the turn.

## Attachments

Markdown images (`![name](url)`) and file links (`[name](url)`) in an Agent Session prompt reach the model as files. This works only for links to `https://uploads.linear.app`, which eve downloads with the Linear access token. eve sends the token only to that exact HTTPS origin, and links to other hosts stay Markdown text.

A file fails when the download fails, an image link returns non-image content, or a file link returns an HTML page. The link then becomes its label, and the agent gets a note that the file couldn't be retrieved. The turn continues.

## API handle

Event handlers receive `channel.linear`. Use its `createActivity`, `listActivities`, and `updateSession` methods to post your own Agent Activities and update the Agent Session.

## Low-level API helpers

Import `callLinearGraphQL` and `createLinearAgentActivity` from
`eve/channels/linear` when a custom integration needs a direct API call.
`callLinearGraphQL<T>({ query, queryName, variables?, credentials?, api? })`
returns the response's `data` as `T` and throws `LinearApiError` for HTTP or
GraphQL errors. `createLinearAgentActivity({ activity, credentials?, api? })`
posts an activity to a Linear Agent Session and returns `{ id, success }`.
Both accept `credentials.accessToken` and optional API overrides; prefer the
channel context's API handle when it already has the configured credentials.

## Custom hooks

Return `{ auth }` to start a turn, or `null` to accept the webhook without waking the agent. Add `title` to set the run title when the event starts a run.

```ts
import { defaultLinearAuth, linearChannel } from "eve/channels/linear";

export default linearChannel({
  onAgentSession: (_ctx, event) => {
    if (event.action !== "created" && event.action !== "prompted") return null;
    return { auth: defaultLinearAuth(event) };
  },
});
```

To restrict the agent to some Linear teams or projects, inspect `event.agentSession.issue`. Return `context` alongside `auth` to add background:

```ts
import { defaultLinearAuth, linearChannel } from "eve/channels/linear";

export default linearChannel({
  onAgentSession: (_ctx, event) => {
    if (event.agentSession.issue?.identifier?.startsWith("OPS-") !== true) return null;
    return {
      auth: defaultLinearAuth(event),
      context: ["Only make reversible changes unless the issue says otherwise."],
    };
  },
});
```

Override event handlers to post your own Agent Activities:

```ts
import { linearChannel } from "eve/channels/linear";

export default linearChannel({
  events: {
    async "content.completed"(event, ctx) {
      if (event.data.phase !== "reply" || typeof event.data.value !== "string") return;
      await ctx.channel.linear.createActivity({
        body: `Done.\n\n${event.data.value}`,
        type: "response",
      });
    },
    async "interaction.opened"(event, ctx) {
      await ctx.channel.linear.createActivity({
        body: event.data.request.prompt,
        type: "elicitation",
      });
    },
  },
});
```

Add session links when your agent creates an external artifact:

```ts
await channel.linear.updateSession({
  addedExternalUrls: [{ label: "Run log", url: "https://example.com/runs/123" }],
});
```

## Proactive sessions

To start a session without a webhook, call `to(linear, target).send(message, { auth })`. The target continues an existing Agent Session by `agentSessionId`, or creates one from an `issueId` or root `commentId`. This example runs from a schedule; a route handler passes the same target to `ctx.to(...)`.

```ts
import { defineSchedule } from "eve/schedules";

import linear from "../channels/linear";

export default defineSchedule({
  cron: "0 14 * * 1",
  async run({ to, waitUntil, appAuth }) {
    waitUntil(
      to(linear, {
        issueId: "EVE-123",
        initialActivity: "Preparing the status update.",
      }).send("Post a concise status update with blockers and next actions.", {
        auth: appAuth,
      }),
    );
  },
});
```

## What to read next

- [Channels overview](/docs/channels): the channel contract and every built-in channel
- [MCP connections](/docs/connections/mcp): use the Linear MCP connection when the agent needs to inspect or edit Linear data from another channel

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)