Linear

Automate Linear issue comment threads with bot responses. Supports both standard comments mode and Linear's app-actor agent sessions.

Install

pnpm add @chat-adapter/linear

Quick start

The adapter auto-detects credentials from LINEAR_API_KEY, LINEAR_ACCESS_TOKEN, LINEAR_CLIENT_CREDENTIALS_*, or LINEAR_CLIENT_ID/LINEAR_CLIENT_SECRET, plus LINEAR_WEBHOOK_SECRET and LINEAR_BOT_USERNAME.

lib/bot.ts
import { Chat } from "chat";
import { createLinearAdapter } from "@chat-adapter/linear";

const bot = new Chat({
  userName: "my-bot",
  adapters: {
    linear: createLinearAdapter(),
  },
});

bot.onNewMention(async (thread, message) => {
  await thread.post("Hello from Linear!");
});

By default, the adapter runs in mode: "comments" and treats Comment webhooks as the inbound message source. For Linear app-actor installs, set mode: "agent-sessions" so inbound handling is driven by AgentSessionEvent.

Configuration

Prop

Type

One of apiKey, accessToken, top-level clientId/clientSecret, or clientCredentials is required, plus webhookSecret.

Authentication

Option A — Personal API key

Best for personal projects or single-workspace bots. Actions are attributed to you as an individual.

  1. Go to Settings then Security & Access.
  2. Under Personal API keys, click Create key.
  3. Choose Only select permissions and enable Create issues + Create comments.
  4. Set LINEAR_API_KEY.
createLinearAdapter({ apiKey: process.env.LINEAR_API_KEY! });

Option B — OAuth access token

Use this when your app already manages the OAuth flow:

createLinearAdapter({ accessToken: process.env.LINEAR_ACCESS_TOKEN! });

Option C — Multi-tenant OAuth installs

Use top-level clientId / clientSecret for Slack-style multi-tenant installs. Each Linear workspace install is stored separately, webhook requests resolve the correct workspace token by organizationId, and withInstallation() lets you target a specific organization outside webhook handling.

  1. Go to Settings then API then Applications.
  2. Create the OAuth2 application.
  3. Note the Client ID and Client Secret.
const adapter = createLinearAdapter({
  clientId: process.env.LINEAR_CLIENT_ID!,
  clientSecret: process.env.LINEAR_CLIENT_SECRET!,
  mode: "agent-sessions",
});

await bot.initialize();
const { organizationId } = await adapter.handleOAuthCallback(request, {
  redirectUri: process.env.LINEAR_REDIRECT_URI!,
});

await adapter.withInstallation(organizationId, async () => {
  await adapter.postMessage("linear:issue-id", "Hello from a background job");
});

Option D — Single-tenant client credentials

App identity without multi-tenant installs. The adapter fetches and refreshes the token automatically.

createLinearAdapter({
  clientCredentials: {
    clientId: process.env.LINEAR_CLIENT_CREDENTIALS_CLIENT_ID!,
    clientSecret: process.env.LINEAR_CLIENT_CREDENTIALS_CLIENT_SECRET!,
    scopes: ["read", "write", "comments:create", "issues:create"],
  },
  mode: "agent-sessions",
});

Advanced

Token encryption

For multi-tenant OAuth installs, pass a base64-encoded 32-byte key as encryptionKey (or set LINEAR_ENCRYPTION_KEY) to encrypt stored access and refresh tokens at rest:

openssl rand -base64 32

When encryptionKey is set, setInstallation() encrypts tokens before writing to the configured state adapter. Existing plaintext records continue to work — you can roll the key in without flushing installs.

Making the bot @-mentionable

To make the bot appear in Linear's @-mention dropdown as an Agent:

  1. In your OAuth app settings, enable Agent session events under webhooks.
  2. Have a workspace admin install the app with actor=app and the app:mentionable scope:
https://linear.app/oauth/authorize?
  client_id=your_client_id&
  redirect_uri=https://your-domain.com/callback&
  response_type=code&
  scope=read,write,comments:create,issues:create,app:mentionable&
  actor=app

Once installed with actor=app, set mode: "agent-sessions" so the adapter treats AgentSessionEvent as the entrypoint:

  • onNewMention fires from session-created events.
  • thread.startTyping() sends an ephemeral Linear thought.
  • thread.post(stream) uses agent activities and session plan updates.
  • Session threads are append-only; sent.edit() / sent.delete() are not supported there.

See the Linear Agents docs for full details.

Direct API client

Access the underlying LinearClient via .linearClient:

const linear = bot.getAdapter("linear").linearClient;
const issue = await linear.issue("ENG-123");

API key, access token, and single-tenant client-credentials modes return the same client anywhere. Multi-tenant OAuth requires webhook-handler context.

The previous .client getter still works as a deprecated alias for .linearClient.

Webhook setup

Webhook management requires workspace admin access. If you don't see the API settings page, ask a workspace admin.

  1. Go to Settings then API then click Create webhook.
  2. Set the URL to https://your-domain.com/api/webhooks/linear.
  3. Copy the Signing secret to LINEAR_WEBHOOK_SECRET.
  4. Under Data change events, select Comments (required for mode: "comments"), Agent session events (required for mode: "agent-sessions"), Issues, and optionally Emoji reactions.
  5. Choose a team selection and click Create webhook.

Thread model

Linear has four thread variants:

TypeDescriptionThread ID format
Issue-levelTop-level comments on an issuelinear:{issueId}
Comment threadReplies nested under a commentlinear:{issueId}:c:{commentId}
Agent session on issueApp-actor session on an issuelinear:{issueId}:s:{agentSessionId}
Agent session on comment threadApp-actor session on a comment threadlinear:{issueId}:c:{commentId}:s:{agentSessionId}

When a user writes a comment, the bot replies within the same comment thread.

Feature support

Messaging

FeatureSupported
Post message
Edit messagePartial
Delete messagePartial
File uploads
StreamingAgent sessions / Post+Edit
Scheduled messages

Rich content

FeatureSupported
Card formatMarkdown
Buttons
Link buttons
Select menus
TablesGFM
Fields
Images in cards
Modals

Conversations

FeatureSupported
Slash commands
Mentions
Add reactions
Remove reactionsPartial
Typing indicatorAgent sessions
DMs
Ephemeral messages
User lookup
Parent subject
Native client
Custom API endpoint

Message history

FeatureSupported
Fetch messages
Fetch single message
Fetch thread info
Fetch channel messages
List threads
Fetch channel info
Post channel message