docs(add-linear): OAuth app auth, bridge patch, team routing, wiring
Rewrite SKILL.md with tested setup: OAuth app with client credentials (recommended), bridge catchAll patch for platforms without @-mention, LINEAR_TEAM_KEY for team-based routing, webhook setup with delay note, private vs public sender policy, and wiring example. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,24 @@ description: Add Linear channel integration via Chat SDK. Issue comment threads
|
|||||||
|
|
||||||
# Add Linear Channel
|
# Add Linear Channel
|
||||||
|
|
||||||
Adds Linear support via the Chat SDK bridge. The agent participates in issue comment threads.
|
Adds Linear support via the Chat SDK bridge. The agent participates in issue comment threads. Every comment on a Linear issue triggers the agent — no @-mention needed.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
**Recommended:** Create a Linear **OAuth application** so the agent posts as an app identity, not as you. This prevents the adapter from filtering your own comments as self-messages.
|
||||||
|
|
||||||
|
1. Go to [Linear Settings > API > OAuth Applications](https://linear.app/settings/api/applications/new)
|
||||||
|
2. Create an app (e.g. "NanoClaw Bot")
|
||||||
|
- Developer URL: your repo URL (e.g. `https://github.com/your-org/nanoclaw`)
|
||||||
|
- Callback URL: `http://localhost`
|
||||||
|
3. After creating, click the app and enable **Client credentials** under grant types
|
||||||
|
4. Copy the **Client ID** and **Client Secret**
|
||||||
|
|
||||||
|
**Alternative:** Use a Personal API Key (`LINEAR_API_KEY`) for simpler setup. The agent will post as you, and your own comments will be filtered (other team members' comments still work).
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
NanoClaw doesn't ship channels in trunk. This skill copies the Linear adapter in from the `channels` branch.
|
NanoClaw doesn't ship channels in trunk. This skill copies the Linear adapter in from the `channels` branch and patches the Chat SDK bridge to support catch-all message forwarding (Linear OAuth apps can't be @-mentioned).
|
||||||
|
|
||||||
### Pre-flight (idempotent)
|
### Pre-flight (idempotent)
|
||||||
|
|
||||||
@@ -18,6 +31,7 @@ Skip to **Credentials** if all of these are already in place:
|
|||||||
- `src/channels/linear.ts` exists
|
- `src/channels/linear.ts` exists
|
||||||
- `src/channels/index.ts` contains `import './linear.js';`
|
- `src/channels/index.ts` contains `import './linear.js';`
|
||||||
- `@chat-adapter/linear` is listed in `package.json` dependencies
|
- `@chat-adapter/linear` is listed in `package.json` dependencies
|
||||||
|
- `src/channels/chat-sdk-bridge.ts` contains `catchAll`
|
||||||
|
|
||||||
Otherwise continue. Every step below is safe to re-run.
|
Otherwise continue. Every step below is safe to re-run.
|
||||||
|
|
||||||
@@ -41,13 +55,42 @@ Append to `src/channels/index.ts` (skip if the line is already present):
|
|||||||
import './linear.js';
|
import './linear.js';
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Install the adapter package (pinned)
|
### 4. Patch the Chat SDK bridge for catch-all message forwarding
|
||||||
|
|
||||||
|
Linear OAuth apps can't be @-mentioned, so the bridge's `onNewMention` handler never fires. Add `catchAll` support to `src/channels/chat-sdk-bridge.ts`:
|
||||||
|
|
||||||
|
**4a.** Add `catchAll?: boolean` to the `ChatSdkBridgeConfig` interface:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
/**
|
||||||
|
* Forward ALL messages in unsubscribed threads, not just @-mentions.
|
||||||
|
* Use for platforms where the bot identity can't be @-mentioned (e.g.
|
||||||
|
* Linear OAuth apps). The thread is auto-subscribed on first message.
|
||||||
|
*/
|
||||||
|
catchAll?: boolean;
|
||||||
|
```
|
||||||
|
|
||||||
|
**4b.** Add this handler block right after the `chat.onNewMention(...)` block (before the DMs block):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Catch-all for platforms where @-mention isn't possible (e.g. Linear
|
||||||
|
// OAuth apps). Forward every unsubscribed message and auto-subscribe.
|
||||||
|
if (config.catchAll) {
|
||||||
|
chat.onNewMessage(/.*/, async (thread, message) => {
|
||||||
|
const channelId = adapter.channelIdFromThreadId(thread.id);
|
||||||
|
await setupConfig.onInbound(channelId, thread.id, await messageToInbound(message));
|
||||||
|
await thread.subscribe();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Install the adapter package (pinned)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install @chat-adapter/linear@4.26.0
|
pnpm install @chat-adapter/linear@4.26.0
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Build
|
### 6. Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm run build
|
pnpm run build
|
||||||
@@ -55,37 +98,71 @@ pnpm run build
|
|||||||
|
|
||||||
## Credentials
|
## Credentials
|
||||||
|
|
||||||
> 1. Go to [Linear Settings > API Keys](https://linear.app/settings/account/security/api-keys/new)
|
### 1. Set up a webhook
|
||||||
> 2. Create a **Personal API Key** (or use an OAuth application for team-wide access)
|
|
||||||
> 3. Copy the API key
|
|
||||||
> 4. Set up a webhook:
|
|
||||||
> - Go to **Settings** > **API** > **Webhooks** > **New webhook**
|
|
||||||
> - URL: `https://your-domain/webhook/linear`
|
|
||||||
> - Select events: **Comment** (created, updated)
|
|
||||||
> - Copy the signing secret
|
|
||||||
|
|
||||||
### Configure environment
|
1. Go to **Linear Settings** > **API** > **Webhooks** > **New webhook**
|
||||||
|
2. Label: `NanoClaw`
|
||||||
|
3. URL: `https://your-domain/webhook/linear` (the shared webhook server, default port 3000)
|
||||||
|
4. Team: select the team you want to monitor
|
||||||
|
5. Events: check **Comment**
|
||||||
|
6. Save — copy the **signing secret**
|
||||||
|
|
||||||
|
Note: Linear webhook delivery may be delayed 1-5 minutes for new webhooks. This is normal.
|
||||||
|
|
||||||
|
### 2. Configure environment
|
||||||
|
|
||||||
Add to `.env`:
|
Add to `.env`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
LINEAR_API_KEY=lin_api_...
|
# OAuth app (recommended)
|
||||||
LINEAR_WEBHOOK_SECRET=your-webhook-secret
|
LINEAR_CLIENT_ID=your-client-id
|
||||||
|
LINEAR_CLIENT_SECRET=your-client-secret
|
||||||
|
|
||||||
|
# OR Personal API key (simpler, but agent posts as you)
|
||||||
|
# LINEAR_API_KEY=lin_api_...
|
||||||
|
|
||||||
|
LINEAR_WEBHOOK_SECRET=your-webhook-signing-secret
|
||||||
|
LINEAR_BOT_USERNAME=NanoClaw Bot
|
||||||
|
LINEAR_TEAM_KEY=ENG
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- `LINEAR_BOT_USERNAME`: display name for the bot (used for self-message detection when using a Personal API Key)
|
||||||
|
- `LINEAR_TEAM_KEY`: the Linear team key (e.g. `ENG`, `NAN`). Find it in Linear under Settings > Teams. All issues in this team route to one messaging group.
|
||||||
|
|
||||||
Sync to container: `mkdir -p data/env && cp .env data/env/env`
|
Sync to container: `mkdir -p data/env && cp .env data/env/env`
|
||||||
|
|
||||||
|
## Wiring
|
||||||
|
|
||||||
|
Ask the user: **Is this a private or public Linear workspace?**
|
||||||
|
|
||||||
|
- **Private workspace** — use `unknown_sender_policy: 'public'`. Only workspace members can comment.
|
||||||
|
- **Public workspace** — use `unknown_sender_policy: 'strict'` and add trusted members (see GitHub skill for member registration example).
|
||||||
|
|
||||||
|
Run `/manage-channels` to wire the Linear channel to an agent group, or insert manually:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Create messaging group (one per team)
|
||||||
|
INSERT INTO messaging_groups (id, channel_type, platform_id, name, is_group, unknown_sender_policy, created_at)
|
||||||
|
VALUES ('mg-linear-eng', 'linear', 'linear:ENG', 'Engineering', 1, 'public', datetime('now'));
|
||||||
|
|
||||||
|
-- Wire to agent group
|
||||||
|
INSERT INTO messaging_group_agents (id, messaging_group_id, agent_group_id, trigger_rules, response_scope, session_mode, priority, created_at)
|
||||||
|
VALUES ('mga-linear-eng', 'mg-linear-eng', '<your-agent-group-id>', '', 'all', 'per-thread', 10, datetime('now'));
|
||||||
|
```
|
||||||
|
|
||||||
|
The `platform_id` must be `linear:<TEAM_KEY>` matching the `LINEAR_TEAM_KEY` env var. Use `per-thread` session mode so each issue comment thread gets its own agent session.
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
If you're in the middle of `/setup`, return to the setup flow now.
|
If you're in the middle of `/setup`, return to the setup flow now.
|
||||||
|
|
||||||
Otherwise, run `/manage-channels` to wire this channel to an agent group.
|
Otherwise, restart the service (`systemctl --user restart nanoclaw` or `launchctl kickstart -k gui/$(id -u)/com.nanoclaw`) to pick up the new channel.
|
||||||
|
|
||||||
## Channel Info
|
## Channel Info
|
||||||
|
|
||||||
- **type**: `linear`
|
- **type**: `linear`
|
||||||
- **terminology**: Linear has "teams" containing "issues." Each issue's comment thread is a separate conversation.
|
- **terminology**: Linear has "teams" containing "issues." Each issue's comment thread is a separate conversation.
|
||||||
- **how-to-find-id**: The platform ID is your team key (e.g. `ENG`). Find it in Linear under Settings > Teams. Each issue becomes its own thread automatically.
|
- **how-to-find-id**: The platform ID is `linear:<TEAM_KEY>` (e.g. `linear:ENG`). Find your team key in Linear under Settings > Teams. Each issue becomes its own thread automatically.
|
||||||
- **supports-threads**: yes (issue comment threads are native conversations)
|
- **supports-threads**: yes (issue comment threads are native conversations)
|
||||||
- **typical-use**: Webhook/notification — the agent receives issue comment events and responds in threads
|
- **typical-use**: Webhook-driven — the agent receives all issue comment events and responds automatically. No @-mention needed (Linear OAuth apps can't be @-mentioned).
|
||||||
- **default-isolation**: Typically shares a session with a chat channel (e.g. Slack) so the agent can discuss issues in the same context as team chat. Use a separate agent group if the Linear team tracks sensitive work.
|
- **default-isolation**: Use `per-thread` session mode. Each issue comment thread gets its own isolated agent session.
|
||||||
|
|||||||
Reference in New Issue
Block a user