# Build a Linear agent with Vercel Connect

**Author:** Ben Sabic

---

You can build an AI-powered Linear agent that responds to @-mentions on issues, maintains conversation history, and calls tools autonomously, without storing a long-lived Linear API key or webhook signing secret in your environment. Chat SDK handles the platform integration (e.g., webhooks and message formatting) and AI SDK's `ToolLoopAgent` runs the reasoning loop that lets your agent call tools and act on results. [Vercel Connect](https://vercel.com/connect) issues a Linear access token at runtime and forwards Linear events to your project, keeping credentials scoped to the environments that need them.

You'll scaffold the bot with the `create-chat-sdk` CLI, which generates the Connect wiring for you, then create a Linear connector, link it to your project, and add an agent with tools. Along the way, you'll enable streaming responses, tool calling, and more using Redis and the [Vercel AI Gateway](https://vercel.com/ai-gateway).

> Vercel Connect is in beta and available on all plans. Features and behavior, including available connectors and trigger forwarding, may change before general availability. Usage is subject to the [Beta Agreement](https://vercel.com/docs/release-phases/public-beta-agreement) and [Vercel Connect terms](https://vercel.com/docs/connect/legal).

## Prerequisites

Before you begin, make sure you have:

- Node.js 20+ and a package manager (e.g., [pnpm](https://pnpm.io/))
  
- Access to a Vercel team and project with Vercel Connect enabled
  
- Vercel CLI installed (`npm i -g vercel`)
  
- A Linear workspace where you can install an app (workspace admin access)
  

## How it works

Chat SDK is a unified TypeScript SDK for building chatbots across Slack, Linear, Discord, and other platforms. You register event handlers (like `onNewMention` and `onSubscribedMessage`), and the SDK routes incoming webhooks to them. The Linear adapter handles comment parsing and interactions with Linear's GraphQL API. The Redis state adapter tracks which threads your bot has subscribed to and manages distributed locking to handle concurrent messages.

The `create-chat-sdk` CLI scaffolds a minimal, webhook-only Next.js app:

- Your `Chat` configuration in `src/lib/bot.ts`
  
- The dynamic webhook route at `/api/webhooks/[platform]`    - `.env.example` file    - The adapter dependencies    Passing `--connect` adds the Vercel Connect authentication pieces. AI SDK's `ToolLoopAgent` wraps a language model with tools and runs an autonomous loop: the model generates text or calls a tool, the SDK executes the tool, feeds the result back, and repeats until the model finishes. When you pass a model string like `"anthropic/claude-opus-4.8"`, and host your application on Vercel, the AI SDK will route the request through the AI Gateway automatically. Chat SDK accepts any `AsyncIterable<string>` as a message, so you can pass the agent's `fullStream` directly to `thread.post()` for real-time updates in Linear. The Linear adapter runs in `agent-sessions` mode for this guide. With an app-actor install, your bot appears in Linear's @-mention dropdown as an Agent, and each mention starts an agent session on the issue or comment thread. In this mode, `thread.startTyping()` sends an ephemeral Linear thought, and `thread.post(stream)` posts responses as agent activities and session plan updates. Session threads are append-only, so `sent.edit()` and `sent.delete()` are not supported there. Vercel Connect provides Linear credentials at runtime, so you don't have to manage a static API key or OAuth secret. You register a Linear connector once, link it to your project, and the generated bot spreads the `connectLinearAdapter` helper from `@vercel/connect/chat` into the Linear adapter. Connect also forwards inbound Linear events to a trigger destination you register on the connector. For this agent, that destination is your project at `/api/webhooks/linear`. The helper wires authentication in both directions: - For outbound calls to the Linear API, it supplies an `accessToken` resolver that fetches a fresh, short-lived token per request via Connect's `getToken`.    - For inbound events, Connect verifies them with Linear and forwards them to your app. The helper's `webhookVerifier` then confirms each forwarded event carries a valid Vercel OIDC token, replacing Linear's native webhook signature check.    Because OIDC verification replaces Linear's signed webhook check, request freshness relies on the short-lived OIDC token's expiry, and there is no built-in delivery de-duplication. Keep your webhook handlers idempotent. ## Steps ### 1\. Scaffold the project with create-chat-sdk The `create-chat-sdk` CLI generates the entire Chat SDK skeleton with a single command. Pass the [Linear platform adapter](https://chat-sdk.dev/adapters/official/linear), the [Redis state adapter](https://chat-sdk.dev/adapters/official/redis), and the `--connect` flag to authenticate with Vercel Connect:

`pnpm create chat-sdk@latest my-linear-agent --adapter linear redis --connect -y cd my-linear-agent`

If you use npm, add the `--` separator so npm forwards the flags to the CLI instead of consuming them itself:

`npm create chat-sdk@latest -- my-linear-agent --adapter linear redis --connect -y`

The CLI generates:

- `src/lib/bot.ts` with the `Chat` configuration. Because you passed `--connect`, it spreads the `connectLinearAdapter` helper from `@vercel/connect/chat` into the Linear adapter factory, and `@vercel/connect` is added to your dependencies.
  
- `src/app/api/webhooks/[platform]/route.ts`, the dynamic webhook route that becomes your `POST /api/webhooks/linear` endpoint.    - `.env.example`, which lists a `LINEAR_CONNECTOR` variable for your connector UID in place of `LINEAR_API_KEY` and `LINEAR_WEBHOOK_SECRET`.    - Starter handlers for mentions and subscribed thread replies, which you'll extend with an agent in step five.    The scaffolded app is webhook-only, with no pages or client UI. You'll add the AI SDK and Zod yourself, since the agent loop is your code rather than part of the scaffold: `pnpm add ai zod` The `ai` package is the AI SDK, and includes the [AI Gateway provider](https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway). `zod` is used to define tool input schemas.

* * *

The [Vercel Plugin](https://vercel.com/docs/agent-resources/vercel-plugin) turns your AI coding agent (e.g., OpenAI Codex, Claude Code, or Cursor) into a Vercel expert. It adds skills, slash commands, and current knowledge of the tools this template uses, including Vercel Connect, Chat SDK, and AI SDK.

The plugin is optional; it isn't required to build your Linear agent or to follow this guide. Note that `create-chat-sdk` detects when it's run by a coding agent and runs non-interactively, so agents should pass adapters with `--adapter` as shown above.

`npx plugins add vercel/vercel-plugin`

### 2\. Create a Linear connector with Vercel Connect

Vercel Connect creates and manages the Linear OAuth app for you. You don't register an application in Linear's settings, manage a redirect URI, or copy any credentials. You create a connector in the Vercel dashboard, set its scopes and events there, and install it in your workspace.

Open the [Connect page](https://vercel.com/d?to=%2F%5Bteam%5D%2F~%2Fconnect) on your team's dashboard, then click Create Connector to start the Add Connection flow. Once you've done that:

1. Choose Linear as the provider.
   
2. Select your Linear workspace and name the app (e.g., `acme-linear`). If you haven't connected a Linear workspace yet, connect and authorize one first, then return to the [Connect page](https://vercel.com/d?to=%2F%5Bteam%5D%2F~%2Fconnect). Keep Triggers enabled so Linear events reach your project.
   
3. Open Advanced and set:
   
   - Scopes the agent needs: `read`, `write`, `comments:create`, `issues:create`, and `app:mentionable`. The `app:mentionable` scope is what makes the bot appear in Linear's @-mention dropdown as an Agent.
     
   - Trigger Event Types to forward: Agent session events, so both new mentions and follow-up prompts in a session reach your agent. Optionally add Comments and Emoji reactions if your bot handles those.
     
4. Click Create Linear Connector, then Install to your Linear Workspace. The app is installed as an app actor, so it acts as the application itself rather than as an individual user.
   
5. In the connector's settings, link it to your project, select the environments it applies to (e.g., production), and register your project as a trigger destination with the path `/api/webhooks/linear`, the route the CLI generated in step one.
   

You can also do this from the CLI. Create the connector with trigger forwarding enabled, then attach your project and register the trigger destination:

`vercel connect create linear --name acme-linear --triggers vercel connect attach linear/acme-linear \ --project my-linear-agent --environment production \ --triggers --trigger-path /api/webhooks/linear`

Because Connect manages the Linear app, Linear delivers events to Connect's intake URL (shown in the connector settings), not directly to your deployment's `/api/webhooks/linear`. Connect verifies Linear, then forwards each event to the trigger destination you registered.

Copy the generated `.env.example` file and add your connector UID:

`cp .env.example .env.local`

`LINEAR_CONNECTOR=linear/acme-linear`

Replace `linear/acme-linear` with your connector UID from the Connect dashboard or `vercel connect list`. Add the same variable to your Vercel project's [environment variables](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fsettings%2Fenvironment-variables) so deployments can resolve the connector.

### 3\. Provision Redis and pull environment variables

Your agent uses Redis for thread subscriptions and distributed locking. Provision [Upstash Redis](https://vercel.com/marketplace/upstash) and connect it to your project with the Vercel CLI:

`vercel link vercel integration add upstash`

> Other providers are also supported in the Vercel Marketplace, including [Redis](https://vercel.com/marketplace/redis).

`vercel integration add` installs the Upstash integration if it isn't already, provisions a database, connects it to your project, and pulls its connection environment variables into `.env.local`. Follow the prompts to pick the Redis product and a plan.

To use the dashboard instead, open the [Storage](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fstores) page for your project, click Create Database, and follow the flow to add Upstash Redis. Then sync the variables locally:

`vercel env pull`

`vercel env pull` also adds a `VERCEL_OIDC_TOKEN`, which AI SDK uses to authenticate requests to the AI Gateway, so there's no API key to generate or store. `@vercel/connect` reads the same token automatically, both locally and on deployments.

The OIDC token expires after 12 hours, so re-run `vercel env pull` to refresh it, or start the dev server with `vercel dev` to refresh it automatically. Linking the project also lets Vercel Connect resolve the connector when the adapter requests a token.

You should see `REDIS_URL` (from Upstash), `VERCEL_OIDC_TOKEN` (for AI Gateway and `@vercel/connect`), and the `LINEAR_CONNECTOR` value you set earlier. You should not add `LINEAR_API_KEY`, `LINEAR_ACCESS_TOKEN`, or `LINEAR_WEBHOOK_SECRET`.

### 4\. Define your agent's tools

Create `src/lib/tools.ts` with the tools your agent can call. This example defines a weather tool and a docs tool, but you can add any tools your use case requires:

``import { tool } from "ai"; import { z } from "zod"; export const tools = { getWeather: tool({ description: "Get the current weather for a location", inputSchema: z.object({ location: z.string().describe("City name, e.g. San Francisco"), }), execute: async ({ location }) => { // Replace with a real weather API call const response = await fetch( `https://api.weatherapi.com/v1/current.json?key=${process.env.WEATHER_API_KEY}&q=${encodeURIComponent(location)}` ); const data = await response.json(); return { location, temperature: data.current.temp_f, condition: data.current.condition.text, }; }, }), searchDocs: tool({ description: "Search the company documentation for a topic", inputSchema: z.object({ query: z.string().describe("The search query"), }), execute: async ({ query }) => { // Replace with your actual search implementation return { results: [`Result for: ${query}`] }; }, }), };`` Each tool has a `description` (which tells the model when to use it), an `inputSchema` (a Zod schema that the model fills in), and an `execute` function that runs when the tool is called. If a tool calls another provider that Vercel Connect supports (e.g., GitHub), it can request a scoped token for itself. ### 5\. Add the agent to the generated bot The CLI already generated `src/lib/bot.ts` with the Connect wiring and starter handlers. Update it to create a `ToolLoopAgent` and stream its responses from the mention and thread handlers: `import { Chat } from "chat"; import { toAiMessages } from "chat/ai"; import { createLinearAdapter } from "@chat-adapter/linear"; import { createRedisState } from "@chat-adapter/state-redis"; import { ToolLoopAgent } from "ai"; import { connectLinearAdapter } from "@vercel/connect/chat"; import { tools } from "./tools"; const agent = new ToolLoopAgent({ model: "anthropic/claude-opus-4.8", instructions: "You are a helpful AI assistant in a Linear workspace. " + "Answer questions clearly and use your tools when you need " + "real-time data. Keep responses concise and well-formatted for " + "issue comment threads.", tools, }); export const bot = new Chat({ userName: "ai-agent", adapters: { linear: createLinearAdapter({ ...connectLinearAdapter(process.env.LINEAR_CONNECTOR!), mode: "agent-sessions", }), }, state: createRedisState(), }); // Handle first-time mentions bot.onNewMention(async (thread, message) => { await thread.subscribe(); const result = await agent.stream({ prompt: message.text }); await thread.post(result.fullStream); }); // Handle follow-up messages in subscribed threads bot.onSubscribedMessage(async (thread, message) => { const allMessages = []; for await (const msg of thread.allMessages) { allMessages.push(msg); } const history = await toAiMessages(allMessages); const result = await agent.stream({ messages: history }); await thread.post(result.fullStream); });` The `connectLinearAdapter` spread is the part the CLI generated for you. It returns two fields: - An `accessToken` resolver in function form. The adapter calls it on each Linear API request, and it resolves a fresh, short-lived token via Connect's `getToken`. The token's subject is pinned to `{ type: "app" }`, so the agent acts as the application itself, and the scopes you set on the connector determine what the token can do.    - A `webhookVerifier` that validates the Vercel OIDC token Connect attaches to each trigger-forwarded event. The default verifier matches the deployment's project and environment automatically (`projectId` defaults to `VERCEL_PROJECT_ID`, and `environment` to `VERCEL_TARGET_ENV`, then `VERCEL_ENV`), so production, preview, and development each accept only their own tokens. Verification fails closed: if those values are absent, every request is rejected, and the issuer is pinned to `https://oidc.vercel.com`.    Omit `webhookSecret` and don't set `LINEAR_WEBHOOK_SECRET`. When `webhookVerifier` is set, it takes precedence over the signing secret, and the OIDC check is the freshness boundary. Leaving `LINEAR_WEBHOOK_SECRET` unset avoids mixing direct-Linear and Connect modes. The helper also accepts optional `getToken` parameters as a second argument (everything except `subject`), such as `scopes` or `validityBufferMs`, if you need to narrow or tune token requests. Setting `mode: "agent-sessions"` tells the adapter to treat `AgentSessionEvent` webhooks as the inbound message source, which matches the app-actor install Connect created. When someone @-mentions the agent on an issue, Linear starts an agent session and `onNewMention` fires. The handler subscribes to the thread (to track future messages in that session) and streams the agent's response as agent activities. For follow-up messages, `onSubscribedMessage` retrieves the full thread history using `thread.allMessages`, converts it to the AI SDK message format with `toAiMessages`, and passes it to the agent so it has complete conversation context. Using `fullStream` is preferred over `textStream` because it preserves paragraph breaks between tool-calling steps. Chat SDK auto-detects the stream type; in agent sessions it posts updates through Linear's agent activities so users see progress in real time. Keep in mind that session threads are append-only, so your handlers can post new messages but can't edit or delete earlier ones. The CLI also generated the webhook route at `src/app/api/webhooks/[platform]/route.ts`, which exposes `POST /api/webhooks/linear`. It routes each request to the matching adapter's handler and uses `waitUntil` so your event handlers finish processing after the HTTP response is sent, which is required on serverless platforms where the function would otherwise terminate early. You don't need to modify it. With trigger forwarding enabled, Connect POSTs verified Linear payloads to this route, and the helper's `webhookVerifier` runs before the adapter parses the body. ### Custom webhook verification (optional) The default verifier accepts only the deployment's own environment. If you forward events to more than one environment, build a verifier with `createConnectWebhookVerifier` and override the field: `import { connectLinearAdapter, createConnectWebhookVerifier, } from "@vercel/connect/chat"; createLinearAdapter({ ...connectLinearAdapter(process.env.LINEAR_CONNECTOR!), mode: "agent-sessions", webhookVerifier: createConnectWebhookVerifier({ environment: ["production", "preview"], }), });` Avoid hardcoding `environment: "production"` unless you only forward to production, since that would reject preview and development deployments. ### 6\. Test the agent Linear sends events to Connect, which forwards them to a deployed Vercel project rather than to your machine. You test the full round trip against a preview or development deployment, with no local tunnel to spin up. Your app rejects direct Linear POSTs unless you add a separate direct-webhook path with `LINEAR_WEBHOOK_SECRET`. 1. Deploy a preview build to receive the Linear events:     `vercel` 1. In the [connector's settings](https://vercel.com/d?to=%2F%5Bteam%5D%2F~%2Fconnect?service=linear), make sure that deployment's environment is linked and registered as the trigger destination at `/api/webhooks/linear`. The default verifier only accepts tokens for the deployment's own environment, so the connector must forward to the environment you're testing.
   
2. Open an issue in your Linear workspace and @-mention the agent from the mention dropdown (e.g., `@AI Agent`).
   
3. Ask it, "What's the weather in San Francisco?". You should see a streaming response appear in the agent session on the issue.
   

### 7\. Deploy to Production

Once you've tested your agent, deploy it to production:

`vercel --prod`

Your Linear AI agent is now live and will respond to mentions in your workspace.

## Troubleshooting

### Agent doesn't respond to mentions

Check that your Linear connector has trigger forwarding enabled and that your project is registered as a trigger destination with the correct path (`/api/webhooks/linear`). Confirm the connector is installed in your workspace and that its Trigger Event Types include Agent session events. Verify the `app:mentionable` scope is enabled on the connector; without it, the bot won't appear in Linear's @-mention dropdown. You can review all of this in the connector's settings. Verify production/preview deployment logs on `/api/webhooks/linear` for 401s before debugging token issues.

### Token requests fail or return unauthorized

Make sure the project is linked (`vercel link`) and that the connector is linked to it for the current environment. Confirm `LINEAR_CONNECTOR` is set to the correct connector UID in the environment, the connector is installed in your workspace, and the scopes the agent uses are enabled on the connector. You can check the connector's link, environments, and scopes in its settings. For local development, run `vercel env pull` to refresh `VERCEL_OIDC_TOKEN`, since it expires after 12 hours.

### Webhook returns 401 / Invalid signature

- Confirm `connectLinearAdapter` is spread into `createLinearAdapter`, so the helper's `webhookVerifier` is active.
  
- Confirm OIDC Federation is enabled on the project. The verifier fails closed, so if `VERCEL_PROJECT_ID` or the other required environment variables are missing, then every request is rejected.
  
- Remove `LINEAR_WEBHOOK_SECRET` from the project if set (it can force the wrong verification path in some setups).
  
- Confirm the request is coming from Connect (trigger destination configured), not from Linear hitting your app directly.
  
- If you forward to multiple environments, the default verifier rejects tokens from environments other than the deployment's own. Override it with `createConnectWebhookVerifier({ environment: [...] })` as shown above.    ### Missing Authorization bearer token - The event reached your app without Connect forwarding (either a Linear webhook pointing directly to your deployment or a trigger destination not registered).    - Fix the trigger path to `/api/webhooks/linear` and link the correct environment.    ### Duplicate replies in a session OIDC verification replaces Linear's native webhook signature check, and Connect has no built-in delivery de-duplication. If a forwarded event is delivered more than once, your handlers run multiple times. Keep webhook handlers idempotent, for example by tracking processed event IDs in Redis. ### Edits or deletes fail in a session Agent session threads in Linear are append-only, so `sent.edit()` and `sent.delete()` are not supported there. Post a new message instead of editing an earlier one. If your bot also handles standard comment threads, edits and deletes work in that mode. ### Streaming appears choppy or delayed In agent-sessions mode, Chat SDK streams responses through Linear's agent activities. If you're seeing issues, check that your Redis connection is stable, as the SDK uses distributed locks to manage concurrent messages. ### Tool calls fail silently If the agent calls a tool but no result appears, check for errors in your tool's `execute` function. AI SDK surfaces tool execution errors back to the model, which may attempt to recover. Add error handling in your tools and check your server logs for details. ### Thread history grows too large For long-running sessions, the conversation history can exceed the model's context window. Consider limiting the number of messages you pass to the agent by slicing the history array or by using a summarization step for older messages. ## Related resources - [Vercel Connect overview](https://vercel.com/docs/connect)
  
- [Vercel Connect quickstart guide](https://vercel.com/docs/connect/quickstart)
  
- [Vercel Connect SDK reference](https://vercel.com/docs/connect/ts-sdk-reference)
  
- [Chat SDK CLI reference](https://chat-sdk.dev/docs/create-chat-sdk)
  
- [Chat SDK Vercel Connect integration docs](https://chat-sdk.dev/docs/vercel-connect)
  
- [Chat SDK Linear adapter docs](https://chat-sdk.dev/adapters/official/linear)
  
- [Chat SDK streaming](https://chat-sdk.dev/docs/streaming)
  
- [Linear Agents documentation](https://linear.app/developers/agents)
  
- [AI SDK agent documentation](https://ai-sdk.dev/docs/agents/building-agents)
  
- [AI Gateway documentation](https://vercel.com/docs/ai-gateway)

---

[View full KB sitemap](/kb/sitemap.md)
