{"slug":"add-linear","title":"add-linear","summary":"Add Linear channel integration via Chat SDK. Issue comment threads as conversations.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-08-24T05:37:16.898661Z","repo":{"url":"https://github.com/nanocoai/nanoclaw","stars":30841,"forks":12823,"license":"MIT","updatedAt":"2026-09-23T17:25:39Z"},"bodyHtml":"<hr>\n<h2>name: add-linear\ndescription: Add Linear channel integration via Chat SDK. Issue comment threads as conversations.</h2>\n<h1>Add Linear Channel</h1>\n<p>Adds Linear support via the Chat SDK bridge. The agent participates in issue\ncomment threads. Every comment on a Linear issue triggers the agent — no\n@-mention needed. NanoClaw doesn't ship channels in trunk — this skill copies the\nLinear adapter in from the <code>channels</code> branch.</p>\n<p>The mechanical steps under <strong>Apply</strong> carry <code>nc:</code> directive fences: an agent reads\nthe prose and applies them, and a parser can apply them deterministically from\nthe same document. Every directive is idempotent, so the whole skill is safe to\nre-run; anything a parser can't apply falls back to the prose beside it.</p>\n<h2>Prerequisites</h2>\n<p><strong>Recommended:</strong> Create a Linear <strong>OAuth application</strong> so the agent posts as an app identity, not as you. This prevents the adapter from filtering your own comments as self-messages.</p>\n<ol>\n<li>Go to <a href=\"https://linear.app/settings/api/applications/new\">Linear Settings &gt; API &gt; OAuth Applications</a></li>\n<li>Create an app (e.g. \"NanoClaw Bot\")\n<ul>\n<li>Developer URL: your repo URL (e.g. <code>https://github.com/your-org/nanoclaw</code>)</li>\n<li>Callback URL: <code>http://localhost</code></li>\n</ul>\n</li>\n<li>After creating, click the app and enable <strong>Client credentials</strong> under grant types</li>\n<li>Copy the <strong>Client ID</strong> and <strong>Client Secret</strong></li>\n</ol>\n<p><strong>Alternative:</strong> Use a Personal API Key (<code>LINEAR_API_KEY</code>) for simpler setup. The agent will post as you, and your own comments will be filtered (other team members' comments still work).</p>\n<h2>Apply</h2>\n<p>Linear OAuth apps post and read comments under an app identity that can't be\n@-mentioned; the adapter's declared channel defaults therefore respond to plain\ncomments rather than mention-only, and the wiring below sets that same pattern\nmode explicitly.</p>\n<h3>1. Copy the adapter and its registration test</h3>\n<p>Fetch the <code>channels</code> branch and copy the Linear adapter and its registration\ntest into <code>src/channels/</code> (overwrite — the branch is canonical):</p>\n<pre><code>src/channels/linear.ts\nsrc/channels/linear-registration.test.ts\n</code></pre>\n<h3>2. Register the adapter</h3>\n<p>Append the self-registration import to the channel barrel (skipped if the line\nis already present). This one line is the skill's only reach-in into the channel\nregistry:</p>\n<pre><code>import './linear.js';\n</code></pre>\n<h3>3. Install the adapter package</h3>\n<p>Pinned to an exact version — the supply-chain policy rejects ranges and <code>latest</code>:</p>\n<pre><code>@chat-adapter/linear@4.29.0\n</code></pre>\n<h3>4. Build and validate</h3>\n<p>Build first: it guards the typed <code>createChatSdkBridge(...)</code> core call and proves\nthe dependency is installed. Then run the one integration test.</p>\n<pre><code>pnpm run build\n</code></pre>\n<pre><code>pnpm exec vitest run src/channels/linear-registration.test.ts\n</code></pre>\n<p>Both must be clean before proceeding. <code>linear-registration.test.ts</code> imports the\nreal channel barrel and asserts the registry contains <code>linear</code>. It goes red if\nthe <code>import './linear.js';</code> line is deleted or drifts, if the barrel fails to\nevaluate, or if <code>@chat-adapter/linear</code> isn't installed (the import throws) — so\nit also covers the dependency from step 3. End-to-end message delivery against a\nreal Linear workspace is verified manually once the service is running — see\nWiring and Next Steps.</p>\n<h2>Credentials</h2>\n<p>Linear app and webhook setup is human and interactive — these steps are prose\n(no parser can click through the Linear UI), except the final env write.</p>\n<h3>1. Set up a webhook</h3>\n<ol>\n<li>Go to <strong>Linear Settings</strong> &gt; <strong>API</strong> &gt; <strong>Webhooks</strong> &gt; <strong>New webhook</strong></li>\n<li>Label: <code>NanoClaw</code></li>\n<li>URL: <code>https://your-domain/webhook/linear</code> (the shared webhook server, default port 3000)</li>\n<li>Team: select the team you want to monitor</li>\n<li>Events: check <strong>Comment</strong></li>\n<li>Save — copy the <strong>signing secret</strong></li>\n</ol>\n<p>Note: Linear webhook delivery may be delayed 1-5 minutes for new webhooks. This is normal.</p>\n<h3>2. Store the credentials</h3>\n<p>Capture the values, then write them. <code>prompt</code> only <em>asks</em> and binds the answer\nto a name; a separate directive consumes it. Here they go to <code>.env</code>\n(set-if-absent — a value you've already filled in is never overwritten) and sync\nto the container.</p>\n<p>Use <strong>either</strong> the OAuth app credentials (recommended) <strong>or</strong> a Personal API key.\nFor the API-key path, paste <code>none</code> at the OAuth prompts and set <code>LINEAR_API_KEY</code>\nin <code>.env</code> by hand (commented in the template below). <code>LINEAR_BOT_USERNAME</code> is the\ndisplay name for the bot, used for self-message detection when using a Personal\nAPI Key. <code>LINEAR_TEAM_KEY</code> is the Linear team key (e.g. <code>ENG</code>, <code>NAN</code>) — find it\nin Linear under Settings &gt; Teams; all issues in this team route to one messaging\ngroup.</p>\n<pre><code>Paste the OAuth Client ID — Linear Settings &gt; API &gt; OAuth Applications. Paste `none` if using a Personal API key instead.\n</code></pre>\n<pre><code>Paste the OAuth Client Secret. Paste `none` if using a Personal API key instead.\n</code></pre>\n<pre><code>Paste the webhook signing secret from the webhook you just created.\n</code></pre>\n<pre><code>Enter the Linear team key (e.g. `ENG`, `NAN`) — Settings &gt; Teams.\n</code></pre>\n<pre><code>Enter the bot display name (e.g. `NanoClaw Bot`).\n</code></pre>\n<pre><code>LINEAR_CLIENT_ID={{linear_client_id}}\nLINEAR_CLIENT_SECRET={{linear_client_secret}}\nLINEAR_WEBHOOK_SECRET={{linear_webhook_secret}}\nLINEAR_TEAM_KEY={{linear_team_key}}\nLINEAR_BOT_USERNAME={{linear_bot_username}}\n</code></pre>\n<p>If you went the Personal API key route, add this line to <code>.env</code> instead of the\nOAuth pair (agent posts as you, your own comments are filtered):</p>\n<pre><code>LINEAR_API_KEY=lin_api_...\n</code></pre>\n<h2>Wiring</h2>\n<p>Linear is team-routed: the assistant watches one team and answers <em>every</em> comment\non its issues (it can't be @-mentioned). Wire the team you set up to an agent —\npick which one should answer (<code>ncl groups list</code> shows their folders). The host\nservice must be running — <code>ncl</code> connects to it over a Unix socket.</p>\n<p>The sender policy depends on the workspace: a private workspace can use <code>public</code>\n(only workspace members can comment anyway); a public workspace should use\n<code>strict</code> so only registered members may talk to the agent.</p>\n<pre><code>Which agent should answer Linear comments? Enter its folder (run `ncl groups list`).\n</code></pre>\n<pre><code>Is this a private or public Linear workspace? Enter `public` for a private workspace (only members can comment) or `strict` for a public workspace (only registered members may talk to the agent).\n</code></pre>\n<pre><code>ncl messaging-groups create --channel-type linear --platform-id linear:{{linear_team_key}} --is-group 1 --unknown-sender-policy {{linear_sender_policy}} --name {{linear_team_key}}\nncl wirings create --channel-type linear --platform-id linear:{{linear_team_key}} --agent-group {{agent_folder}} --engage-mode pattern --engage-pattern . --session-mode per-thread\n</code></pre>\n<p>The explicit <code>pattern</code> engage mode with pattern <code>.</code> matches the Linear adapter's\ndeclared channel defaults — Linear can't be @-mentioned, so the agent answers\nevery comment. Each issue thread becomes its own conversation. There's no\nwelcome — Linear has no direct message, so the assistant greets people when it\nfirst answers a comment. If you chose <code>strict</code>, register the people who may talk\nto the agent (see the GitHub skill for adding members).</p>\n<h2>Next Steps</h2>\n<p>If you're in the middle of <code>/setup</code>, return to the setup flow now.</p>\n<p>Otherwise, restart the service to pick up the new channel.</p>\n<p>Run from your NanoClaw project root:</p>\n<pre><code>source setup/lib/install-slug.sh\nlaunchctl kickstart -k gui/$(id -u)/$(launchd_label)  # macOS\nsystemctl --user restart $(systemd_unit)              # Linux\n</code></pre>\n<h2>Channel Info</h2>\n<ul>\n<li><strong>type</strong>: <code>linear</code></li>\n<li><strong>terminology</strong>: Linear has \"teams\" containing \"issues.\" Each issue's comment thread is a separate conversation.</li>\n<li><strong>how-to-find-id</strong>: The platform ID is <code>linear:&lt;TEAM_KEY&gt;</code> (e.g. <code>linear:ENG</code>). Find your team key in Linear under Settings &gt; Teams. Each issue becomes its own thread automatically.</li>\n<li><strong>supports-threads</strong>: yes (issue comment threads are native conversations)</li>\n<li><strong>typical-use</strong>: Webhook-driven — the agent receives all issue comment events and responds automatically. No @-mention needed (Linear OAuth apps can't be @-mentioned).</li>\n<li><strong>default-isolation</strong>: Use <code>per-thread</code> session mode. Each issue comment thread gets its own isolated agent session.</li>\n</ul>\n<h2>Troubleshooting</h2>\n<p><strong>Comments never reach the agent.</strong> New Linear webhooks can lag 1–5 minutes, so wait before digging. Then check the webhook in Linear Settings → API → Webhooks: the URL must be your public host at <code>/webhook/linear</code> (shared webhook server, port 3000), the right team selected, and the <strong>Comment</strong> event checked. A mismatch between the webhook's signing secret and <code>LINEAR_WEBHOOK_SECRET</code> makes deliveries fail signature verification silently — re-copy the secret from the webhook page.</p>\n<p><strong>OAuth credentials rejected.</strong> The Client ID and Secret come from Linear Settings → API → OAuth Applications, and the app must have <strong>Client credentials</strong> enabled under grant types after creation — without that toggle the token exchange 401s. If you meant to use a Personal API key instead, answer <code>none</code> at both OAuth prompts and set <code>LINEAR_API_KEY</code> in <code>.env</code> by hand.</p>\n<p><strong>The agent ignores your own comments.</strong> That's Personal-API-key mode working as designed: comments from the key's account are filtered as self-messages so the bot doesn't answer itself. Other members' comments still trigger it; if it must answer you too, switch to the OAuth app identity.</p>\n<p><strong>Sender-policy answer rejected, or issues route nowhere.</strong> The policy must be exactly <code>public</code> or <code>strict</code> (lowercase), and <code>LINEAR_TEAM_KEY</code> must be the short team key (e.g. <code>ENG</code>) from Settings → Teams — all issues in that one team route to the messaging group.</p>\n<p><strong>Wired but dead.</strong> Run <code>pnpm exec vitest run src/channels/linear-registration.test.ts</code> — red means the barrel import or the <code>@chat-adapter/linear</code> install drifted, so re-run the Apply steps. If green, restart the service (see Next Steps) so the adapter and <code>.env</code> values are live.</p>\n","files":[{"path":"apply-fixtures.json","sizeBytes":606,"isText":true},{"path":"REMOVE.md","sizeBytes":815,"isText":true},{"path":"SKILL.md","sizeBytes":10265,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"human-reviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"human-reviewed","screen":{"ran":true,"outcome":"flagged-cleared-by-moderator","suspicious":2,"notes":1,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-08-24T05:38:49.443321Z","sha256":"CA08FDE110FBEDC3C0FF7A5ACD3D11CA5D51B5F2051AE4522203F2AD30E5FDE3","sizeBytes":5206},"review":null,"source":{"repositoryUrl":"https://github.com/nanocoai/nanoclaw","path":".claude/skills/add-linear","license":"MIT","commit":"143db6c907c652773a536c7c9e96269fdad0a4a4","subtreeSha":"5B281C0E2314A3532B7BBD308B3E67EEE50C3BD478C83B37A32B151C62D246CF","lastSyncedAt":"2026-09-24T06:48:46.168681Z"},"reviewedAt":"2026-08-25T15:12:30.784808Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/nanocoai/nanoclaw/tree/main/.claude/skills/add-linear"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install nanocoai-nanoclaw@llmmart"},{"target":"git","command":"git clone https://github.com/nanocoai/nanoclaw.git"}]}