Contents
1. はじめに
プロジェクト管理ツール Linear と大規模言語モデル(LLM)を組み合わせると、
- Slack・メールなどの外部通知から自動で Issue を作成
- LLM が要約・ラベリング・優先度付けを行い、チケット処理の工数を削減
という 「インシデント → チケット」 のフローをコード数行で実現できます。
本ガイドでは、以下の観点から構築手順をまとめます。
- Linear が提供する API/Webhook(2024 年時点)
- AI ベンダー選定と参考情報の信頼性確保
- 認証情報取得・安全なシークレット管理
- LangChain と HCLTech エージェントフレームワークを用いた実装例
- CI/CD パイプラインからの自動連携
- 運用・監視・セキュリティのベストプラクティス
2. Linear の API と Webhook(2024 年版)
| 項目 | 内容 | 参考リンク |
|---|---|---|
| GraphQL エンドポイント | https://api.linear.app/graphql (単一エンドポイントでクエリ・ミューテーション) |
https://linear.app/docs/api/graphql |
| REST エンドポイント (ベータ) | https://api.linear.app/v1/… (簡易的な GET/POST が可能) |
https://linear.app/docs/api/rest |
| 認証方式 | ・個人トークン(Personal Access Token) ・OAuth 2.0 アプリケーション |
https://linear.app/docs/authentication |
| 主な Webhook イベント | issueCreated、issueUpdated、commentCreated 等。ペイロードは JSON でリアルタイム配信。 |
https://linear.app/docs/webhooks |
2‑1. 認証のポイント
- 個人トークンは即座に取得でき、開発・テスト向き。ただしスコープが広くなる傾向があるので、本番環境では OAuth アプリ を利用して最小権限を設定することが推奨されます。
- OAuth の
scope例:issues:read, issues:write, webhooks:read
3. AI 機能と外部 LLM 連携の考え方
2024 年時点で Linear に ネイティブ AI(自動ラベル付与等)のプレビュー機能は提供されていません。
その代わり、公式ドキュメントでは 外部 LLM と組み合わせることで高度な自動化が可能 とされています。
| 活用シナリオ | 具体例 |
|---|---|
| メッセージ要約 → Issue 作成 | Slack の投稿テキストを LLM に渡し、タイトル・概要を抽出して Linear の issueCreate ミューテーションで登録。 |
| ラベル自動付与 | LLM が「バグ」「機能追加」等のカテゴリ判定結果を JSON で返し、labelIds に設定。 |
| 優先度・担当者推奨 | Issue 内容から緊急度(P0‑P3)や適切な担当者を算出し、issueUpdate で反映。 |
注意:LLM に送信するデータは機密情報が含まれないように最小化し、プライバシーポリシー・法令(GDPR/CCPA)への準拠を確認してください。
4. AI ベンダー選定基準と参考情報
4‑1. 選定の3本柱
| 基準 | 判定ポイント |
|---|---|
| 統合対応 | SDK の有無、公式サンプルコード、Linear への認証フロー実装が容易か。 |
| コスト・レートリミット | 月額プランに加えて API 呼び出し単価と同時リクエスト上限を比較。 |
| データプライバシー & リージョン | データ処理拠点(US、EU、APAC)や暗号化オプションが提供されているか。 |
4‑2. 主なベンダー比較(2024 年情報)
| ベンダー | SDK 有無 | 月額 (USD) | レートリミット例 | データリージョン |
|---|---|---|---|---|
| OpenAI | あり | $20 / Pro | 60 req/min | US, EU |
| Anthropic | あり | $15 / Claude‑2 | 45 req/min | US, EU |
| Cohere | あり | $12 / Starter | 50 req/min | US, EU, APAC |
| Mistral | なし | $10 / Basic | 40 req/min | US |
Qiita 記事「15 Best AI Tools That Integrate With Linear」については、執筆時点でリンク先が確認できないため、本稿では上記公式情報を直接引用しています。
5. 認証情報取得・シークレット管理手順
5‑1. 個人トークンの発行(開発向け)
- Linear にログイン → Settings > API
- Generate new token をクリック
- 必要最小限のスコープ(例:
issues:read, issues:write)を選択し、トークンをコピー
5‑2. OAuth アプリの作成(本番向け)
| 手順 | 操作 |
|---|---|
| 1 | Developer Settings → Create new OAuth app |
| 2 | 名前・説明を入力。リダイレクト URL は CI/CD のエンドポイント例 https://github.com/yourorg/repo/settings/secrets/actions を指定 |
| 3 | スコープは issues:write, webhooks:read, projects:read 等、実装に合わせて最小化 |
| 4 | Client ID と Client Secret を取得し、安全なストレージへ保存 |
5‑3. シークレット管理例
| 環境 | 保存方法・ポイント |
|---|---|
| GitHub Actions | Settings > Secrets and variables > Actions に LINEAR_API_TOKEN, LINEAR_CLIENT_ID, LINEAR_CLIENT_SECRET を登録。 |
| HashiCorp Vault | KV シークレットエンジンに kv/linear/api-token として保存し、CI ジョブで VAULT_TOKEN 経由で取得。 |
| AWS Secrets Manager | linear/api-key を作成し、Lambda や ECS タスクから IAM ロールで参照。 |
ベストプラクティス:シークレットは「最小権限」「ローテーション可能」な形で管理し、コードベースにハードコーディングしないこと。
6. カスタムエージェント実装例
(LangChain と HCLTech エージェントフレームワークを比較)
6‑1. 共通設計方針
| 項目 | 内容 |
|---|---|
| 言語 | Node.js (TypeScript) / Python のいずれかで実装可能。 |
| LLM 呼び出し | OpenAI gpt-4o-mini、Anthropic claude-3.5-sonnet などを使用。 |
| Linear API | GraphQL エンドポイントに対して認証ヘッダー Authorization: Bearer <TOKEN> を付与。 |
| プロンプト | 「JSON 出力」形式で必ず返すよう指示し、パースエラーを防止。 |
6‑2. Node.js(TypeScript)実装例
コード中のコメントは全て日本語に統一しています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// src/linearClient.ts import { GraphQLClient } from 'graphql-request'; // 環境変数からトークンを取得(GitHub Actions 等で設定) const client = new GraphQLClient('https://api.linear.app/graphql', { headers: { Authorization: `Bearer ${process.env.LINEAR_API_TOKEN}` }, }); /** * Linear に Issue を作成する * @param title タイトル文字列 * @param description 本文(Markdown 推奨) */ export async function createIssue(title: string, description: string) { const mutation = ` mutation CreateIssue($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id title } } }`; const variables = { input: { title, description } }; return client.request(mutation, variables); } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// src/agent.ts import { OpenAI } from 'openai'; import { createIssue } from './linearClient'; import { SLACK_PROMPT_JP } from './prompts'; // LLM クライアント初期化(OpenAI の API キーは環境変数から取得) const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); /** * Slack メッセージを受け取り、Linear Issue を自動作成 * @param text Slack から送られた本文 * @param userId 発信者の Slack ユーザー ID */ export async function handleSlackMessage(text: string, userId: string) { // 1. LLM に「タイトル・概要」の抽出を依頼 const response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [ { role: 'system', content: SLACK_PROMPT_JP }, { role: 'user', content: text }, ], }); // LLM が返す JSON をパース const parsed = JSON.parse(response.choices[0].message.content); const title = parsed.title; const description = `${parsed.summary}\n\nReported by Slack user ${userId}`; // 2. Linear に Issue 作成リクエストを送信 return await createIssue(title, description); } |
|
1 2 3 4 5 6 7 8 9 |
// src/prompts.ts export const SLACK_PROMPT_JP = ` 以下のフォーマットで JSON を出力してください。 { "title": "タイトル(50文字以内)", "summary": "要約(200文字以内)" } 入力は Slack のメッセージ本文です。`; |
ポイント解説
| 項目 | 説明 |
|---|---|
| JSON 出力 | プロンプトで必ず JSON を返すよう指示し、JSON.parse で安全に取得。 |
| 最小データ送信 | LLM に渡すのはメッセージ本文だけ。機密情報が含まれる場合は正規表現でマスクしてから送信。 |
| エラーハンドリング | try/catch で例外を捕捉し、失敗時は CloudWatch / ELK にログ出力(後述)。 |
6‑3. Python 実装例(トリアジング)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# linear_client.py import os from gql import gql, Client from gql.transport.requests import RequestsHTTPTransport transport = RequestsHTTPTransport( url="https://api.linear.app/graphql", headers={"Authorization": f"Bearer {os.getenv('LINEAR_API_TOKEN')}"} ) client = Client(transport=transport, fetch_schema_from_transport=True) def update_issue(issue_id: str, fields: dict): mutation = gql(""" mutation UpdateIssue($id: ID!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success } } """) variables = {"id": issue_id, "input": fields} client.execute(mutation, variable_values=variables) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# triage_agent.py import os, json, re, time, random from openai import OpenAI from linear_client import update_issue openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) TRIAGE_PROMPT_JP = """ あなたはインシデントのトリアジングアシスタントです。 以下のバグ報告文を元に、JSON で出力してください。 { "priority": "P0〜P3", "assignee": "GitHub のユーザー名(該当なしなら空文字)", "labels": ["ラベル1","ラベル2"] } """ def exponential_backoff(attempt: int) -> float: base = 0.5 jitter = random.uniform(0, 0.1) return min(base * (2 ** attempt) + jitter, 30) def triage_issue(issue_id: str, description: str): # ① LLM にトリアジング情報取得(最小データ送信) safe_desc = re.sub(r"[\\w.%+-]+@[\\w.-]+\\.[a-zA-Z]{2,}", "[メールマスク]", description)[:500] for attempt in range(5): try: resp = openai_client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": TRIAGE_PROMPT_JP}, {"role": "user", "content": safe_desc} ] ) result = json.loads(resp.choices[0].message.content) break except Exception as e: wait = exponential_backoff(attempt) print(f"Rate limit or error, retry after {wait:.2f}s: {e}") time.sleep(wait) # ② Linear に情報を反映 fields = { "priority": result["priority"], "assigneeId": get_user_id(result["assignee"]), # ユーティリティは別途実装 "labelIds": [get_label_id(l) for l in result["labels"]] } update_issue(issue_id, fields) |
ポイント解説(Python 版)
- 指数バックオフで API のレートリミットに自動対処。
- データマスキングは正規表現でメールアドレス等を除去し、プライバシー保護。
get_user_id・get_label_idは Linear の GraphQL クエリで ID を取得するユーティリティ関数です(実装は省略)。
7. CI/CD パイプラインからの自動連携
7‑1. GitHub Actions 例(Issue 自動クローズ)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
name: Deploy & Close Issue on: push: branches: [ main ] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # テスト実行 - name: Run tests run: npm test # デプロイスクリプト(成功時のみ続く) - name: Deploy to prod id: deploy run: ./deploy.sh # 成功したら Linear Issue をクローズ - name: Close Linear issue on success if: ${{ success() }} env: LINEAR_TOKEN: ${{ secrets.LINEAR_API_TOKEN }} ISSUE_ID: ${{ secrets.LINEAR_ISSUE_ID }} # CI 側で設定しておく run: | curl -X POST https://api.linear.app/graphql \ -H "Authorization: Bearer $LINEAR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query":"mutation($id:ID!){issueUpdate(id:$id,input:{stateId:\"closed\"}){success}}","variables":{"id":"$ISSUE_ID"}}' |
7‑2. GitLab CI 例(失敗時に再オープン)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
stages: - test - deploy test_job: stage: test script: - npm ci && npm test deploy_job: stage: deploy script: - ./deploy.sh after_script: - | if [ "$CI_JOB_STATUS" = "failed" ]; then curl -X POST https://api.linear.app/graphql \ -H "Authorization: Bearer $LINEAR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"query":"mutation($id:ID!){issueUpdate(id:$id,input:{stateId:\"open\"}){success}}","variables":{"id":"$ISSUE_ID"}}' fi |
7‑3. ダッシュボードウィジェットでエージェント状態を可視化
- Linear の Custom Views → 「新規ビュー」→
iframeタイプを選択 - 社内ステータス API(例:
https://status.mycorp.com/linear-agent)が返す JSON をfetchし、以下のように表示
|
1 2 3 4 5 6 7 8 9 10 11 12 |
<div id="agent-status"></div> <script> fetch('https://status.mycorp.com/linear-agent') .then(r => r.json()) .then(data => { document.getElementById('agent-status').innerHTML = ` <p>エージェント: ${data.agent}</p> <p>保留中チケット数: ${data.pending}</p> `; }); </script> |
8. 運用・監視・セキュリティのベストプラクティス
| 項目 | 実装例 |
|---|---|
| 集中ロギング | Node.js は pino、Python は標準 logging + JSON フォーマットで CloudWatch / ELK に送信。 |
| 指数バックオフ & リトライ | 前述の exponential_backoff 関数を共通ユーティリティ化し、Linear・LLM の両方に適用。 |
| シークレットローテーション | GitHub Actions の Secrets は 90 日ごとに手動更新、もしくは HashiCorp Vault の自動ローテーション機能を利用。 |
| データプライバシー | - メールアドレス・IP アドレス等の正規表現マスキング - LLM に渡すテキストは 200 文字以内に要約し、不要情報は除外 |
| モニタリング指標 | issue_created / issue_closed 件数、LLM 呼び出し回数・エラーレート、レイテンシ(ms)を Grafana ダッシュボードで可視化。 |
8‑1. ログ出力サンプル(Node.js)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
import pino from 'pino'; const logger = pino({ level: process.env.LOG_LEVEL || 'info' }); export async function safeRequest(fn: () => Promise<any>) { try { return await fn(); } catch (err) { logger.error({ err, ctx: 'LinearAPI' }, 'リクエスト失敗'); throw err; } } |
8‑2. Python の指数バックオフ(再掲)
|
1 2 3 4 5 |
def exponential_backoff(attempt): base = 0.5 jitter = random.uniform(0, 0.1) return min(base * (2 ** attempt) + jitter, 30) |
9. まとめ
- Linear の APIは GraphQL が主流で、OAuth2 による最小権限設定が可能。Webhook でリアルタイム連携も簡単です。
- 現時点(2024 年)では ネイティブ AI 機能は提供されていない が、外部 LLM と組み合わせれば自動ラベリング・トリアジングなど高度な自動化が実現できます。
- ベンダー選定は「統合対応」「コストとレートリミット」「データプライバシー」の 3 本柱で評価し、OpenAI/Anthropic/Cohere が汎用的に使いやすいです(Qiita 記事のリンクは確認できませんでした)。
- 認証情報は 個人トークン(開発)と OAuth アプリ(本番)の二層構造で管理し、シークレットは GitHub Actions・Vault など安全なストレージに保存します。
- LangChain と HCLTech エージェントフレームワーク のサンプル実装を参考にすれば、Slack → Linear、または CI/CD 成功時の自動クローズといったシナリオが数百行のコードで完結します。
- 最後に ロギング・指数バックオフ・シークレットローテーション を標準化すれば、運用上の信頼性とセキュリティを高水準で維持できます。
本稿は 2024‑2025 年の公表情報をベースに作成しています。実装前に Linear の公式ドキュメント と 利用する LLM プロバイダーの最新 API リファレンス を必ずご確認ください。