Channels

Linear

Reach your agent through Linear Agent Sessions, with native Agent Activities for progress, questions, and responses, and Vercel Connect credentials.

The Linear channel uses Linear's Agent Session surface rather than ordinary comments. Users delegate work to the agent from Linear, eve receives AgentSessionEvent webhooks at /eve/v1/linear, and the channel replies with native Agent Activities, including thought, action, elicitation, response, and error. Credentials can run through Vercel Connect, which manages the Linear app, its access token, and inbound webhook verification, so there's no API key or webhook secret for you to hold. See Channels for the contract this builds on.

Guided Connect setup

Run the registry setup from the agent directory:

eve add linear

Select Linear Channel in the component checklist. Linear MCP is also selected by default if you want the agent to search and update Linear through MCP. Run eve add channel/linear-agent instead to install only the channel directly.

The flow signs you in to Vercel when needed, creates or links a Vercel project, reuses a compatible existing connector when available or provisions an app-scoped Linear Connect client, and registers /eve/v1/linear as a trigger destination. It then installs @vercel/connect and writes agent/channels/linear.ts with the connector UID.

Vercel Connect creates the Linear app with the app:assignable and app:mentionable scopes required for Agent Sessions, receives and verifies AgentSessionEvent webhooks, and forwards them to the deployed agent. After deploying, open the Linear app in the Connect dashboard and install it in the workspace where you want to delegate work. Then delegate an issue or mention the agent in a Linear Agent Session.

The generated channel uses Connect-managed credentials:

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

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

connectLinearCredentials returns { accessToken, webhookVerifier }: eve uses the Connect-managed app token for Linear GraphQL calls and verifies Connect-forwarded webhooks by their Vercel OIDC signature instead of a Linear webhook secret. Token rotation, refresh, and multi-workspace tenancy stay inside Connect, so there is no LINEAR_AGENT_ACCESS_TOKEN or LINEAR_WEBHOOK_SECRET to manage.

Bring your own Linear app

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

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,
  },
});

Direct webhook verification accepts timestamps within 60 seconds by default, as Linear recommends. Set maxSkewMs to a larger number of milliseconds only when you intentionally accept delayed retries. Use the narrowest window that fits your retry policy.

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

The sample passes credentials explicitly. To rely on env vars instead, drop the credentials block: the access token falls back to LINEAR_AGENT_ACCESS_TOKEN, LINEAR_ACCESS_TOKEN, LINEAR_API_KEY, or LINEAR_API_TOKEN, and the webhook secret falls back to LINEAR_WEBHOOK_SECRET. Both fields also accept lazy resolver functions.

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

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

For Linear's agent surface, configure the OAuth authorize URL with actor=app and grant the app scopes that let it appear as an agent in Linear, including app:assignable and app:mentionable. Subscribe to the AgentSessionEvent webhook category so Linear sends created events when the agent is delegated or mentioned and prompted events when the user continues the session.

Linear sends webhook signatures in Linear-Signature; eve verifies the HMAC over the raw body and rejects stale webhookTimestamp values. If a trusted gateway verifies Linear before the request reaches eve, pass credentials.webhookVerifier instead of a webhook secret. Your custom verifier must enforce its timestamp policy because maxSkewMs does not apply.

How the channel handles messages

Dispatch

The default hook dispatches created and prompted Agent Session events. The Linear context includes app_user_id when available, plus the agent session, issue, comment, and organization identifiers. The session continues with agent-session:<id>.

Delivery

Turn start posts an ephemeral thought, tool calls post ephemeral action activities, final assistant text posts a durable response, and failures post error activities. When the model emits text before a tool call, eve buffers the first non-empty line and uses it as the next ephemeral Linear thought, mirroring Slack's typing-status behavior.

Human-in-the-loop (HITL)

Human-in-the-loop (HITL) input requests render as Linear elicitation activities. When the user replies to the Agent Session, the channel resolves that prompt back to the pending eve input request and resumes with inputResponses.

Connection authorization

When a user-scoped connection needs authorization, the default channel posts Linear's native auth elicitation with the provider's sign-in URL, targeted to the Linear user who started the session. URL-less device flows render their instructions and user code as a plain elicitation. After authorization finishes, the channel posts a thought with the outcome; successful authorization indicates that the parked turn is resuming.

Proactive sessions

Start a session without an inbound webhook with receive(linear, { target }). See Proactive sessions below for the target shape and examples.

Attachments

Markdown images hosted at https://uploads.linear.app in Agent Session prompts are fetched with the resolved Linear access token and included as image file parts. eve sends the bearer token only to that exact HTTPS origin; images from other hosts remain markdown text. If a Linear upload fails or returns non-image content, eve preserves its markdown reference and continues the text turn. Other inbound file attachments are not supported on this channel today.

API handle

Event handlers receive channel.linear, which exposes createActivity, listActivities, and updateSession for custom Agent Activity delivery and Agent Session metadata.

Custom hooks

Return { auth } to dispatch, or null to acknowledge without waking the agent. Return title alongside auth to set the title when the dispatch starts a run.

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) };
  },
});

Restrict dispatch to a subset of Linear teams or projects by inspecting event.agentSession.issue in onAgentSession. Add extra context by returning context alongside auth.

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 delivery when you want more specific Agent Activities.

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

export default linearChannel({
  events: {
    async "message.completed"(eventData, channel) {
      if (eventData.finishReason === "tool-calls" || !eventData.message) return;
      await channel.linear.createActivity({
        body: `Done.\n\n${eventData.message}`,
        type: "response",
      });
    },
    async "input.requested"(eventData, channel) {
      await channel.linear.createActivity({
        body: eventData.requests.map((request) => request.prompt).join("\n\n"),
        type: "elicitation",
      });
    },
  },
});

Add session-level links when your agent creates an external artifact.

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

Proactive sessions

Use the channel's proactive target to continue an existing Agent Session or create one from a Linear issue or root comment. The target accepts an existing agentSessionId, or an issueId or root commentId to create a new session before sending the message. The example below runs from a schedule; a route handler uses the same target shape through ctx.to(...).

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,
      }),
    );
  },
});

For issue or comment targets, the channel calls Linear's proactive Agent Session mutations before starting the eve turn. For an existing agentSessionId, it skips session creation and only seeds the continuation token.

  • Channels overview: the channel contract and every built-in channel
  • MCP connections: use the Linear MCP connection when the agent needs to inspect or edit Linear data from another channel