Skip to main content

Installation

npm install @chat-adapter/linear

Environment Variables

Get your credentials from Linear API Settings.
VariableRequiredDescription
LINEAR_API_KEYAPI Key modePersonal API key from Linear
LINEAR_ACCESS_TOKENOAuth modeOAuth access token
LINEAR_CLIENT_IDApp modeOAuth client ID
LINEAR_CLIENT_SECRETApp modeOAuth client secret
LINEAR_WEBHOOK_SECRETYesWebhook secret for signature verification
LINEAR_BOT_USERNAMEOptionalBot username for display

Configuration Options

type LinearAdapterConfig = 
  | LinearAdapterAPIKeyConfig
  | LinearAdapterOAuthConfig
  | LinearAdapterAppConfig;

// Personal API Key
interface LinearAdapterAPIKeyConfig {
  apiKey: string;
  webhookSecret: string;
  userName: string;
  logger: Logger;
}

// OAuth Access Token
interface LinearAdapterOAuthConfig {
  accessToken: string;
  webhookSecret: string;
  userName: string;
  logger: Logger;
}

// Client Credentials (OAuth App)
interface LinearAdapterAppConfig {
  clientId: string;
  clientSecret: string;
  webhookSecret: string;
  userName: string;
  logger: Logger;
}

Setup

For personal projects or testing:
import { Chat } from 'chat';
import { createLinearAdapter } from '@chat-adapter/linear';
import { MemoryState } from '@chat-adapter/state-memory';

const chat = new Chat({
  userName: 'my-bot',
  adapters: {
    linear: createLinearAdapter({
      apiKey: process.env.LINEAR_API_KEY!,
      webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,
    }),
  },
  state: new MemoryState(),
});

await chat.initialize();

Webhook Handler

app.post('/webhooks/linear', async (req, res) => {
  const response = await linear.handleWebhook(req, {
    waitUntil: (promise) => {/* handle async work */},
  });
  res.status(response.status).send(await response.text());
});
Configure webhook in Linear:
  1. Go to Settings → Workspace → Webhooks
  2. Create webhook with URL: https://your-app.com/webhooks/linear
  3. Add webhook secret
  4. Subscribe to events: Comment (create), Reaction (create/delete)

Features

Supported Events

  • Comment (create) - New comments on issues
  • Reaction (create/delete) - Emoji reactions on comments

Thread Types

Linear adapter supports two thread types: 1. Issue-level threads
linear:{issueId}
All top-level comments on an issue. 2. Comment-level threads
linear:{issueId}:c:{commentId}
Replies to a specific comment (nested thread).

Posting Comments

Post to issue:
// Thread ID: linear:ISS-123
await thread.post('This looks good to me!');
Reply to a comment thread:
// Thread ID: linear:ISS-123:c:comment-abc
await thread.post('Thanks for the clarification.');

Markdown Support

Linear supports markdown in comments:
await thread.post(`
## Update

Fixed the following:
- [x] Bug in login flow
- [x] Performance issue
- [ ] Documentation
`);

Reactions

Add reactions to comments:
await thread.addReaction(commentId, '👍');
await thread.addReaction(commentId, { name: 'thumbs_up' });
removeReaction() is not fully supported. Linear requires the reaction ID, which would need an additional API call to look up.

Cards

Cards are converted to markdown:
import { Card, Section } from 'chat/cards';

await thread.post(
  <Card title="Status Update">
    <Section text="All tasks completed for this sprint" />
  </Card>
);
// Renders as formatted markdown

Thread IDs

Linear thread IDs encode issue ID and optional comment ID:
linear:{issueId}[:c:{commentId}]
Examples:
  • Issue: linear:PROJ-123
  • Comment thread: linear:PROJ-123:c:abc123

Message History

Fetch comments from an issue or comment thread:
const { messages, nextCursor } = await thread.fetchMessages({
  limit: 50,
  direction: 'backward',
});

Threading Behavior

Linear webhooks include parent comment information:
  • Root comment (no parentId): Creates new comment-level thread
  • Reply (has parentId): Routes to parent’s thread
This ensures all replies to a comment are grouped together.

Platform Limits

  • Comment length: ~10,000 characters (no official limit documented)
  • API rate limits: Linear rate limits

Code Examples

chat.onNewMessage(async (event) => {
  if (event.message.text.includes('approved')) {
    await event.thread.addReaction(event.message.id, '✅');
  }
});

Troubleshooting

  • Verify LINEAR_WEBHOOK_SECRET matches Linear webhook settings
  • Check webhook timestamp is within 5 minutes (prevent replay attacks)
  • Ensure request body is raw (not parsed)
  • Enable “Comment” webhook event in Linear settings
  • Verify webhook URL is publicly accessible (HTTPS required)
  • Check API key/token has comments:create scope
  • Grant comments:create scope for adding reactions
  • Use Unicode emoji strings or emoji names
  • Note: removeReaction() has limited support
  • Adapter auto-refreshes tokens before expiry
  • If manual refresh needed, restart the adapter
  • Tokens are valid for 30 days

Required Scopes

Linear OAuth Scopes:
  • read - Read issues and comments
  • write - Create/edit comments
  • comments:create - Create comments
  • issues:create - Create issues (if needed)
Personal API keys have full access to your workspace.

Creating a Linear OAuth App

1

Create OAuth app

Go to Linear OAuth Apps and create a new application.
2

Configure scopes

Select required scopes:
  • read
  • write
  • comments:create
3

Get credentials

Copy Client ID and Client Secret.
4

Set redirect URL

Add your OAuth callback URL (if using user authorization flow).

Next Steps

Message Handling

Process comments and build workflows

Linear SDK

Learn about the Linear GraphQL API

Issue Automation

Build automated issue management bots

State Management

Persist data across issue comments