燃え尽き症候群のコードネーム:ベロシティ偽装と戦うLinearキャパシティ駆動アーキテクチャ
現場のベロシティが上がっているのに、なぜかエンジニアの顔色優れず、技術負債の返済が永遠に後回しになる——。
このアンチパターンに陥っている組織の共通点はただ一つ、「人間の稼働時間をリニア(線形)な機械の稼働時間として扱っている」ことだ。
アジャイル開発において、Jiraの重厚長大なボードや、場当たり的なスプリント計画は、エンジニアの認知負荷を高め、暗黙知のサイロ化を加速させる。ここで取り上げる Linear は、その圧倒的なUI/UXの裏側で、APIファーストかつ極限まで洗練されたデータモデルを持っている。
本稿では、Linearの Cycles(サイクル)と Capacity Planning(稼働容量計画)を極限までハックし、単なるタスク管理ツールを「チームの持続可能性を担保する動的リソース制御エンジン」へと昇華させる実践知を叩き込む。
—
1. Linearデータモデルの内部理解:CyclesとLoadの物理的制約
LinearのCycles機能は、単なるタイムボックス(2週間ごとの区切り)ではない。背後では、イシューの推定値(Estimate)、チームメンバーのキャパシティ、そしてスコープクリープ(範囲の拡大)の係数がリアルタイムに計算されている。
予実管理の破綻を防ぐ「Load vs Capacity」の数理
多くのチームが犯す致命的なミスは、メンバーの理論上の最大稼働時間(100%)をキャパシティとして設定することだ。人間の脳のスイッチングコスト、コードレビュー、突発的な障害対応、そしてインシデントポストモーテムの時間を考慮すると、実効キャパシティ(Effective Capacity)は最大でも 65%〜70% が限界値となる。
Linearのサイクル設定では、この「現実の摩擦係数」を織り込んだキャパシティ設計を行う。
[総稼働時間] × [フォーカス係数 (0.65)] = [Linearに投入すべき真のキャパシティ上限]
この原則を無視してストーリーポイントやイシュー数を積み上げると、Linearは容赦なく「Over capacity」の警告を発する。しかし、多くのチームはこの警告を「単なるUIの装飾」として無視し、ベロシティの幻影に酔いしれる。これがバーンアウト(燃え尽き)のトリガーとなる。
—
2. 徹底解剖:Linear API & Webhooksによる動的キャパシティ制御の自動化
UI上の手動入力に頼る組織はスケールしない。ここからは、LinearのGraphQL APIとWebhookを駆使し、チームのベロシティとキャパシティをコードベースで厳密に制御する手法を解説する。
A. リアルタイム・キャパシティ監視スクリプト (TypeScript / Node.js)
以下のスクリプトは、LinearのGraphQL APIを叩き、現在のサイクルのロード状況を取得。キャパシティの80%を超えた時点でSlackへアラートを飛ばし、これ以上のイシュー追加をシステム的にロック(あるいはレビュー対象に指定)するための基盤コードである。
import { LinearClient } from ‘@linear/sdk’;
import axios from ‘axios’;
// Linearクライアントの初期化(Personal Access Tokenを使用)
const linearClient = new LinearClient({ apiKey: process.env.LINEAR_API_KEY });
const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;
async function monitorCycleCapacity(teamId: string) {
try {
// 1. アクティブなサイクルを取得
const teams = await linearClient.teams();
const activeTeam = teams.nodes.find(t => t.id === teamId);
if (!activeTeam) throw new Error(“Team not found”);
const cycles = await activeTeam.cycles({ filter: { endsAt: { gte: new Date().toISOString() } } });
const currentCycle = cycles.nodes[0];
if (!currentCycle) {
console.log(“アクティブなサイクルが見つかりません。”);
return;
}
// 2. サイクルのロード(負荷)とキャパシティを算出
const issues = await currentCycle.issues();
let totalLoad = 0;
issues.nodes.forEach(issue => {
// Estimate(ポイント)が未設定の場合はデフォルト1とする防衛的コード
totalLoad += issue.estimate ?? 1;
});
// Linear上で定義されたキャパシティ(カスタムフィールドやメタデータから取得を想定、ここでは仮に定数)
const maxCapacity = currentCycle.capacity ?? 40;
const loadPercentage = (totalLoad / maxCapacity) 100;
console.log(`[Cycle: ${currentCycle.name}] Load: ${totalLoad} / Capacity: ${maxCapacity} (${loadPercentage.toFixed(1)}%)`);
// 3. 閾値(80%)を超過した場合のアラート発報
if (loadPercentage >= 80) {
await sendSlackAlert(currentCycle.name, totalLoad, maxCapacity, loadPercentage);
}
} catch (error) {
console.error(“Linear Capacity Monitor Error:”, error);
}
}
async function sendSlackAlert(cycleName: string, load: number, capacity: number, percentage: number) {
const message = {
text: `🚨 【Linear キャパシティ警告】 サイクル ${cycleName} が過負荷状態です!\n` +
`• 現在のロード: \`${load}pts\`\n` +
`• 設定キャパシティ: \`${capacity}pts\`\n` +
`• 負荷率: ${percentage.toFixed(1)}%\n` +
`スコープの縮小または次サイクルへのタスク移動を検討してください。`
};
await axios.post(SLACK_WEBHOOK_URL, message);
}
// 実行
monitorCycleCapacity(process.env.LINEAR_TEAM_ID!);
B. 異常検知のWebhookアーキテクチャ
単発のポーリングスクリプトだけでは不十分だ。エンジニアが勝手に「Urgent」ラベルを貼ってサイクルにタスクをねじ込む(スコープクリープの常套手段)行為を防ぐため、Linearの Webhook をAWS LambdaやCloudflare Workersで受け止めるイベント駆動型アーキテクチャを構築する。
- トリガーイベント: `Issue` の `update` または `create`(サイクルへのアサイン時)
- 検証ロジック:
1. 変更されたイシューがアクティブサイクルに含まれているか?
2. その追加によって、チームのキャパシティ上限を突破するか?
3. 突破した場合、自動的にイシューに `Over-Capacity` ラベルを付与し、Slackのテックリードチャンネルにピン留め通知を送る。
この仕組みにより、「誰かが気合で何とかする」という属人化したマネジメント手法を完全に排除できる。
—
3. ベロシティの「正確な計測」とインフレーションの防止
ベロシティはKPIではない。あくまで「予測のための道具」に過ぎない。しかし、多くの現場でベロシティがインフレを起こす。その原因は以下の2点に集約される。
1. ストーリーポイントのインフレ: 難易度が変わっていないのに、工数(時間)換算でポイントをインフレさせる。
2. 完了定義(Definition of Done: DoD)の曖昧さ: 「コードを書いたから完了(1点)」とし、テストやデプロイ、監視設定が後回しになる。
Linearを「真のベロシティ」に最適化するカスタム設定ハック
- Stateの厳密な定義: LinearのWorkflowステートを、単なる `Todo` / `In Progress` / `Done` にしてはならない。
- `Backlog` -> `Refined`(要件定義完了・見積もり済) -> `In Progress` -> `In Review` -> `QA / Staging` -> `Done`
- ベロシティ計算からの除外条件:
Linearの組み込み分析機能において、サイクル終了時に `Done` にならなかったイシューは自動的に次サイクルへロールオーバー(Rollover)される。この際、「ロールオーバーされたイシューのポイントは、過去のベロシティ実績に含めない」 というエンジニアリング規律を徹底する。
未完了タスクをそのまま次スプリントのベロシティ実績に組み込むと、キャパシティ計算が雪だるま式に狂い、チームを破滅へと導く。
—
4. 燃え尽きを防ぐためのLinear運用「5つの鉄則」
最後に、数々の修羅場を潜り抜けてきたアーキテクトとして、組織に実装すべき運用の鉄則を提示する。
1. 「バッファ・イシュー」の強制常駐化
サイクルのキャパシティ(例: 40pts)のうち、必ず20%(8pts分)は「Unplanned / Bug Fix / Tech Debt」枠として最初から空けておく。この枠には機能を実装するタスクを入れてはならない。突発的な障害対応や、日々の細かなリファクタリングをこの枠に吸収させることで、計画の崩壊を防ぐ。
2. 金曜午後のサイクル終了と月曜午後の計画
サイクルの中日が週末を跨ぐような中途半端な設定は、context switching(コンテキストスイッチ)コストを最大化させる。金曜日の終わりにサイクルをクローズし、週末に脳をリセットさせ、月曜の午前中はコードを書かせず「リファインメントとキャパシティプランニングのみ」に時間を費やす。
3. ワークフローのWIP(Work in Progress)制限
Linear自体にはネイティブなWIP制限の厳格なブロック機能はないが、前述のAPI/Webhook拡張を組み合わせることで、「1人あたり同時に持てる `In Progress` イシューは最大2つまで」に制限する。マルチタスクは生産性を殺す最大の毒薬である。
4. 「ベロシティの比較」の禁止
経営層やプロダクトマネージャーが「チームAのベロシティは50なのに、なぜチームBは30なんだ?」という比較を行うことを厳禁とする。ポイントの価値はチームごとに相対的なものであり、絶対値ではない。Linearのダッシュボードを共有する際は、絶対値ではなく 「Capacityに対するLoadの健全性(健康診断)」 のみをメトリクスとして開示する。
5. 「No Overtime」の文化をツールで強制する
サイクル期間中にキャパシティを超えたオーバーワークが発生した場合、テックリードは自動的に次サイクルのスコープを強制的に削らなければならない。「残業して帳尻を合わせた」という成功体験を組織に植え付けた瞬間から、アジャイル開発の死へのカウントダウンが始まる。
—
結び:コードと同様に、リソース管理にも「美しさ」を
優れたアーキテクチャが美しいコードと堅牢なインフラから成り立つように、持続可能な開発組織は「冷徹なまでの現実主義に基づいたリソース設計」の上に成り立つ。
LinearのCyclesとCapacity Planningは、単にタスクを綺麗に並べるための玩具ではない。エンジニアの認知バイアスを排し、組織の持続可能性を限界までハックするための強力な武器である。
感情論でキャパシティを語る時代は終わった。APIを叩き、データを監視し、システム的にチームの心身を守る——それこそが、現代の最高峰のエンジニアリング・マネジメントなのだ。