Linear
Automate Linear issue comment threads with bot responses. Supports both standard comments mode and Linear's app-actor agent sessions.
Create a new bot
Scaffold a Next.js bot with the Linear adapter using create-chat-sdk:
pnpm create chat-sdk@latest my-bot --adapter linear memory -yThe generated app uses the Memory state adapter for local development. To add the adapter to an existing app, start at Install.
Install
pnpm add @chat-adapter/linear @chat-adapter/state-memoryQuick 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.
For managed credentials and webhook verification, see Vercel Connect.
The memory state adapter keeps subscriptions and locks in process memory, which suits local development. Use Redis or PostgreSQL in production.
Then create the webhook route:
The adapter defaults to mode: "comments" and handles Comment webhooks. For Vercel Connect and Linear app-actor installations, we recommend explicitly setting mode: "agent-sessions" to handle AgentSessionEvent webhooks, including mentions and replies within a session.
Authentication
Vercel Connect
Use Vercel Connect to source the Linear access token at runtime instead of storing a long-lived token or OAuth secret. The connectLinearAdapter() helper from @vercel/connect/chat wires an accessToken resolver and a webhookVerifier for Connect trigger-forwarded webhooks:
We recommend agent sessions for Vercel Connect bots. Enable Agent session events on the Linear app and use an app-actor installation. Omitting mode keeps the "comments" default; existing bots do not need to change their configuration. Changing the adapter mode does not change the app's webhook subscriptions or permissions.
accessToken accepts a string or () => string | Promise<string> resolver invoked per API call. When webhookVerifier is set it takes precedence over webhookSecret and LINEAR_WEBHOOK_SECRET.
Create the connector and attach it to your project with the Vercel CLI. --trigger-path registers the Linear webhook route as the trigger destination, and vercel env pull downloads a development OIDC token for local runs:
Without --trigger-branch, Connect forwards webhooks to your production deployment. See Vercel Connect for the full setup.
Personal API key
A personal API key suits personal projects and single-workspace bots. Actions are attributed to you as an individual.
- Go to Settings then Security & Access.
- Under Personal API keys, click Create key.
- Choose Only select permissions and enable Create issues and Create comments.
- Set
LINEAR_API_KEY.
OAuth access token
Use this when your app already manages the OAuth flow:
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.
- Go to Settings then API then Applications.
- Create the OAuth2 application.
- Note the Client ID and Client Secret.
Single-tenant client credentials
Client credentials give the bot an app identity without multi-tenant installs. The adapter fetches and refreshes the token automatically.
Configuration
| Prop | Type | Default | Description |
|---|---|---|---|
apiKey? | string | Personal API key. Auto-detected from `LINEAR_API_KEY`. | |
accessToken? | string | (() => string | Promise<string>) | OAuth access token. Accepts a string, or (Vercel Connect) a resolver invoked per API call. Auto-detected from `LINEAR_ACCESS_TOKEN`. | |
clientId? | string | Multi-tenant OAuth client ID. Auto-detected from `LINEAR_CLIENT_ID`. | |
clientSecret? | string | Multi-tenant OAuth client secret. Auto-detected from `LINEAR_CLIENT_SECRET`. | |
encryptionKey? | string | AES-256-GCM key for encrypting stored OAuth tokens. Auto-detected from `LINEAR_ENCRYPTION_KEY`. | |
clientCredentials? | ClientCredentialsConfig | Single-tenant client credentials config (`clientId`, `clientSecret`, optional `scopes`). | |
mode? | "comments" | "agent-sessions" | "comments" | Set agent-sessions for the recommended Vercel Connect setup with a Linear app-actor installation. |
webhookSecret? | string | Webhook signing secret. Auto-detected from `LINEAR_WEBHOOK_SECRET`. | |
webhookVerifier? | (request, body) => unknown | Promise<unknown> | Custom verifier used in place of `webhookSecret` (e.g. Vercel Connect OIDC). Takes precedence over the secret; required in Connect mode. | |
userName? | string | "linear-bot" | Bot display name. Auto-detected from `LINEAR_BOT_USERNAME`. |
apiUrl? | string | Override the Linear GraphQL API base URL. |
One of apiKey, accessToken (string or Vercel Connect resolver), top-level
clientId/clientSecret, or clientCredentials is required, plus either
webhookSecret or a webhookVerifier.
Environment variables
| Variable | Required | Description |
|---|---|---|
LINEAR_API_KEY | One auth mode required | Personal API key. |
LINEAR_ACCESS_TOKEN | One auth mode required | OAuth access token. |
LINEAR_CLIENT_ID | One auth mode required | Multi-tenant OAuth client ID. Use with LINEAR_CLIENT_SECRET. |
LINEAR_CLIENT_SECRET | One auth mode required | Multi-tenant OAuth client secret. Use with LINEAR_CLIENT_ID. |
LINEAR_CLIENT_CREDENTIALS_CLIENT_ID | One auth mode required | Single-tenant client credentials client ID. Use with LINEAR_CLIENT_CREDENTIALS_CLIENT_SECRET. |
LINEAR_CLIENT_CREDENTIALS_CLIENT_SECRET | One auth mode required | Single-tenant client credentials client secret. |
LINEAR_CLIENT_CREDENTIALS_SCOPES | No | Comma-separated scopes for single-tenant client credentials. |
LINEAR_ENCRYPTION_KEY | No | Base64-encoded 32-byte key for encrypting stored OAuth tokens. |
LINEAR_WEBHOOK_SECRET | Yes, unless using a webhookVerifier | Webhook signing secret. |
LINEAR_BOT_USERNAME | No | Bot display name. Defaults to linear-bot. |
Webhooks
Webhook management requires workspace admin access. If you don't see the API settings page, ask a workspace admin.
- Go to Settings then API and click Create webhook.
- Set the URL to
https://your-domain.com/api/webhooks/linear. - Copy the Signing secret to
LINEAR_WEBHOOK_SECRET. - Under Data change events, select Comments (required for
mode: "comments"), Agent session events (required formode: "agent-sessions"), Issues, and optionally Emoji reactions. - Choose a team selection and click Create webhook.
Making the bot @-mentionable
To make the bot appear in Linear's @-mention dropdown as an Agent:
- In your OAuth app settings, enable Agent session events under webhooks.
- Have a workspace admin install the app with
actor=appand theapp:mentionablescope:
Once installed with actor=app, set mode: "agent-sessions" so the adapter treats AgentSessionEvent as the entrypoint:
onNewMentionfires from session-created events.thread.startTyping()sends an ephemeral Linearthought.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.
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:
When encryptionKey is set, setInstallation() encrypts tokens before writing to the configured state adapter. Existing plaintext records continue to work, so you can roll the key in without flushing installs.
Direct API client
Access the underlying LinearClient through .linearClient:
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.
Message history
For agent-session threads, fetchMessages() throws a ValidationError if the session has no associated issue or its issue ID does not match the issue ID in the thread ID. The adapter checks this before fetching session comments or activities.
Comment mutations
Comment edits, deletions, and reactions retrieve the target comment before writing. The adapter throws a ValidationError if the comment doesn't exist, has no issue, belongs to another issue, or is outside the requested comment thread. The connection needs read access to verify ownership, in addition to the permissions required for the write.
Thread IDs
Linear has four thread variants:
| Type | Description | Thread ID format |
|---|---|---|
| Issue-level | Top-level comments on an issue | linear:{issueId} |
| Comment thread | Replies nested under a comment | linear:{issueId}:c:{commentId} |
| Agent session on issue | App-actor session on an issue | linear:{issueId}:s:{agentSessionId} |
| Agent session on comment thread | App-actor session on a comment thread | linear:{issueId}:c:{commentId}:s:{agentSessionId} |
When a user writes a comment, the bot replies within the same comment thread.
Feature support
Messaging
| Feature | Supported |
|---|---|
| Post message | |
| Message replies | |
| Edit message | Partial |
| Delete message | Partial |
| File uploads | |
| Streaming | Agent sessions / Post+Edit |
| Scheduled messages |
Rich content
| Feature | Supported |
|---|---|
| Card format | Markdown |
| Buttons | |
| Link buttons | |
| Select menus | |
| Tables | GFM |
| Charts | |
| Fields | |
| Images in cards | |
| Modals |
Conversations
| Feature | Supported |
|---|---|
| Slash commands | |
| Mentions | |
| Add reactions | |
| Remove reactions | Partial |
| Typing indicator | Agent sessions |
| Mark as read | |
| Message edit events | |
| Message delete events | |
| DMs | |
| Ephemeral messages | |
| User lookup | |
| Parent subject | |
| Native client | |
| Custom API endpoint |
Message history
| Feature | Supported |
|---|---|
| Fetch messages | |
| Fetch single message | |
| Fetch thread info | |
| Fetch channel messages | |
| List threads | |
| Fetch channel info | |
| Post channel message |