T-CREATOR

Cline トラブルシュート:無限思考/暴走コマンドを止める安全装置の作り方

Cline トラブルシュート:無限思考/暴走コマンドを止める安全装置の作り方

Cline を使用した開発において、最も深刻な問題の一つが無限思考ループと暴走コマンドです。これらの問題は、開発者の意図しないところで発生し、システムリソースを大量に消費し、時には重要なデータを危険にさらす可能性があります。

本記事では、Cline の暴走を事前に防ぎ、発生時に迅速に対処するための実践的な安全装置の構築方法を詳しく解説いたします。適切な安全装置を設定することで、安心して Cline を活用した開発を進めることができるでしょう。

背景

Cline の動作メカニズム

Cline は、AI 駆動のコーディングアシスタントとして、ユーザーからの指示を受けて自動的にコードの生成や修正を行います。その動作プロセスは以下のような流れで進行します。

まずは、Cline の基本的な動作フローを図で確認しましょう。

mermaidflowchart TD
    user[ユーザー入力] --> parse[指示解析]
    parse --> think[思考プロセス]
    think --> decision{実行判断}
    decision -->|実行| command[コマンド実行]
    decision -->|継続思考| think
    command --> result[結果取得]
    result --> evaluate[結果評価]
    evaluate --> loop_check{継続必要?}
    loop_check -->|Yes| think
    loop_check -->|No| done[完了]

この図で示される通り、Cline は思考プロセスと実行プロセスを繰り返しながら、目標の達成を目指します。正常な場合、適切なタイミングで完了判定が行われ、処理が終了します。

無限思考ループが発生する理由

無限思考ループは、主に以下の要因によって発生します。これらの要因を理解することで、適切な対策を講じることができるでしょう。

目標設定の曖昧さ

ユーザーの指示が曖昧である場合、Cline は完了条件を明確に判断できません。例えば「コードを改善して」という指示では、どの程度改善すれば十分なのかが明確でないため、延々と改善を続ける可能性があります。

複雑な依存関係

プロジェクトの依存関係が複雑で、一つの変更が他の部分に影響を与え続ける場合、Cline は問題解決のために無限に思考し続けることがあります。

フィードバックループの発生

Cline が生成したコードやコマンドの結果が、さらなる問題を引き起こし、それを解決するためにまた新たな処理を開始するという悪循環が発生することがあります。

暴走コマンドの典型的なパターン

暴走コマンドには、いくつかの典型的なパターンが存在します。これらのパターンを知ることで、早期に問題を発見し対処することが可能になります。

ファイル操作の繰り返し

同じファイルに対して繰り返し読み書きを行うパターンです。特に大きなファイルに対する操作が繰り返されると、ディスク I/O が大量に発生し、システム全体のパフォーマンスに影響を与えます。

無制限なディレクトリ探索

プロジェクト内のファイルを探索する際に、適切な制限がかかっていない場合、大量のファイルを読み込み続ける可能性があります。

外部 API の連続呼び出し

外部 API を使用する処理において、レート制限やエラーハンドリングが適切でない場合、API への過度な負荷やコスト増加につながります。

課題

無限思考ループの具体的な問題

無限思考ループが発生すると、開発環境に深刻な影響を及ぼします。まず、システムリソースの観点から見ていきましょう。

CPU 使用率の異常な増加

Cline の思考プロセスは、継続的に CPU リソースを消費します。無限ループ状態では、CPU の使用率が異常に高くなり、他のアプリケーションの動作に悪影響を与えます。特に開発環境では、IDE やビルドツールなど、CPU を多用するアプリケーションが同時に動作しているため、システム全体が重くなる傾向があります。

メモリリークの発生

長時間にわたる思考プロセスでは、メモリの使用量が徐々に増加し、最終的にシステムメモリを枯渇させる可能性があります。これにより、システムの安定性が損なわれ、最悪の場合はクラッシュを引き起こすこともあります。

思考プロセスによるリソース消費の様子を以下の図で示します。

mermaidgraph LR
    subgraph "正常処理"
        normal_start[開始] --> normal_think[思考]
        normal_think --> normal_decision[判断]
        normal_decision --> normal_end[完了]
    end

    subgraph "無限ループ"
        loop_start[開始] --> loop_think[思考]
        loop_think --> loop_decision[判断]
        loop_decision --> loop_think
        loop_think -.->|CPU/メモリ増加| resource_exhaustion[リソース枯渇]
    end

この図から分かるように、正常な処理では明確な完了点があるのに対し、無限ループでは思考と判断を繰り返し続け、リソースを消費し続けます。

暴走コマンドによるシステム負荷

暴走コマンドは、システムレベルでの深刻な問題を引き起こす可能性があります。これらの問題を詳しく見ていきましょう。

ディスク I/O の過負荷

ファイル操作が繰り返し実行されると、ディスクへの読み書きが大量に発生します。SSD の場合は書き込み回数の制限により寿命が短縮される可能性があり、HDD の場合は物理的な負荷によりパフォーマンスが大幅に低下します。

ネットワーク帯域の圧迫

外部 API やリモートリポジトリへのアクセスが連続して発生すると、ネットワーク帯域を大量に消費し、他の通信に影響を与える可能性があります。

ファイルシステムの破損リスク

大量のファイル操作が同時に実行されると、ファイルシステムの整合性に問題が生じる可能性があります。特に、同じファイルに対する同時アクセスが発生すると、データの破損につながるリスクがあります。

開発効率への影響

無限思考ループや暴走コマンドは、開発プロセス全体に悪影響を及ぼします。

開発作業の中断

システムリソースが枯渇すると、開発に必要な他のツールが正常に動作しなくなります。IDE の動作が重くなったり、ビルドプロセスが失敗したりすることで、開発作業が強制的に中断されることがあります。

作業進捗の喪失

Cline が暴走している間に行った作業が適切に保存されない場合、それまでの進捗が失われる可能性があります。また、意図しない変更がファイルに加えられることで、以前の安定した状態に戻すのが困難になることもあります。

デバッグの困難さ

暴走状態では、どの処理が問題を引き起こしているのかを特定するのが困難になります。複数のプロセスが同時に実行されている状況では、問題の根本原因を見つけ出すのに多大な時間を要することがあります。

解決策

思考制限の設定方法

Cline の思考プロセスを制御するために、複数の制限設定を組み合わせることが重要です。これらの設定により、無限思考ループを効果的に防ぐことができます。

思考回数の上限設定

最も基本的な対策として、一つのタスクに対する思考回数の上限を設定します。以下の設定例をご覧ください。

javascript// Cline設定ファイル(.clinerc.json)
{
  "limits": {
    "maxThinkingSteps": 50,
    "maxThinkingTime": 300000,
    "emergencyStop": true
  }
}

この設定では、思考ステップを最大 50 回、思考時間を最大 5 分(300,000 ミリ秒)に制限しています。emergencyStopオプションを有効にすることで、制限に達した場合の緊急停止機能も併せて設定できます。

時間ベースの制限

思考プロセスの実行時間に基づいた制限も効果的です。以下のコードで時間ベースの制限を実装できます。

javascript// 時間制限付き思考プロセス
class ThinkingTimer {
  constructor(maxDuration = 300000) {
    // 5分
    this.maxDuration = maxDuration;
    this.startTime = null;
  }

  start() {
    this.startTime = Date.now();
    this.timerId = setTimeout(() => {
      this.forceStop();
    }, this.maxDuration);
  }

  stop() {
    if (this.timerId) {
      clearTimeout(this.timerId);
      this.timerId = null;
    }
  }

  forceStop() {
    console.log(
      '思考プロセスが時間制限により停止されました'
    );
    process.exit(1);
  }
}

複雑度ベースの制限

タスクの複雑度に応じて、思考制限を動的に調整する方法も有効です。

typescriptinterface TaskComplexity {
  simple: number;
  medium: number;
  complex: number;
}

const thinkingLimits: TaskComplexity = {
  simple: 10, // 簡単なタスクは10回まで
  medium: 30, // 中程度のタスクは30回まで
  complex: 50, // 複雑なタスクは50回まで
};

function getThinkingLimit(
  taskType: keyof TaskComplexity
): number {
  return thinkingLimits[taskType];
}

コマンド実行の監視システム

コマンドの実行を監視し、異常な動作を検出するシステムを構築することで、暴走コマンドを早期に発見できます。

実行回数の監視

同じコマンドが短時間で繰り返し実行されることを検出する仕組みを作りましょう。

javascriptclass CommandMonitor {
  constructor() {
    this.commandHistory = new Map();
    this.warningThreshold = 5; // 5回で警告
    this.blockThreshold = 10; // 10回でブロック
    this.timeWindow = 60000; // 1分間のウィンドウ
  }

  recordCommand(command) {
    const now = Date.now();
    const key = this.normalizeCommand(command);

    if (!this.commandHistory.has(key)) {
      this.commandHistory.set(key, []);
    }

    const history = this.commandHistory.get(key);
    history.push(now);

    // 古い記録を削除
    this.cleanOldRecords(history, now);

    return this.checkThresholds(key, history.length);
  }

  normalizeCommand(command) {
    // コマンドを正規化(パスや引数の違いを吸収)
    return command.split(' ')[0];
  }

  cleanOldRecords(history, now) {
    const cutoff = now - this.timeWindow;
    while (history.length > 0 && history[0] < cutoff) {
      history.shift();
    }
  }

  checkThresholds(command, count) {
    if (count >= this.blockThreshold) {
      return {
        action: 'block',
        message: `コマンド ${command} がブロックされました`,
      };
    } else if (count >= this.warningThreshold) {
      return {
        action: 'warn',
        message: `コマンド ${command} の実行回数が多すぎます`,
      };
    }
    return { action: 'allow' };
  }
}

リソース使用量の監視

システムリソースの使用量を監視し、異常な増加を検出する機能も重要です。

javascriptconst os = require('os');

class ResourceMonitor {
  constructor() {
    this.cpuThreshold = 80; // CPU使用率80%で警告
    this.memoryThreshold = 90; // メモリ使用率90%で警告
    this.monitoring = false;
  }

  startMonitoring(interval = 5000) {
    if (this.monitoring) return;

    this.monitoring = true;
    this.monitorInterval = setInterval(() => {
      this.checkResources();
    }, interval);
  }

  stopMonitoring() {
    if (this.monitorInterval) {
      clearInterval(this.monitorInterval);
      this.monitoring = false;
    }
  }

  checkResources() {
    const cpuUsage = this.getCPUUsage();
    const memoryUsage = this.getMemoryUsage();

    if (cpuUsage > this.cpuThreshold) {
      this.triggerAlert('cpu', cpuUsage);
    }

    if (memoryUsage > this.memoryThreshold) {
      this.triggerAlert('memory', memoryUsage);
    }
  }

  getCPUUsage() {
    // CPU使用率の計算(簡略化)
    const loadAvg = os.loadavg()[0];
    const cpuCount = os.cpus().length;
    return (loadAvg / cpuCount) * 100;
  }

  getMemoryUsage() {
    const totalMem = os.totalmem();
    const freeMem = os.freemem();
    return ((totalMem - freeMem) / totalMem) * 100;
  }

  triggerAlert(type, value) {
    console.warn(
      `⚠️ ${type.toUpperCase()}使用率が異常です: ${value.toFixed(
        2
      )}%`
    );
    // 必要に応じて緊急停止処理を実行
  }
}

緊急停止機能の実装

システムが異常状態に陥った場合に、迅速に停止できる機能を実装します。

キーボードショートカットによる緊急停止

javascriptconst readline = require('readline');

class EmergencyStop {
  constructor() {
    this.setupKeyboardHandlers();
  }

  setupKeyboardHandlers() {
    // Ctrl+C での緊急停止
    process.on('SIGINT', () => {
      this.performEmergencyStop('SIGINT');
    });

    // Ctrl+Z での一時停止
    process.on('SIGTSTP', () => {
      this.performPause();
    });
  }

  performEmergencyStop(signal) {
    console.log(
      `\n🚨 緊急停止が実行されました (${signal})`
    );

    // 実行中のプロセスを停止
    this.stopAllProcesses();

    // 重要なデータを保存
    this.saveEmergencyState();

    // 安全にプロセスを終了
    process.exit(0);
  }

  stopAllProcesses() {
    // 実行中の子プロセスをすべて停止
    console.log('実行中のプロセスを停止しています...');
    // 実装はシステムに依存
  }

  saveEmergencyState() {
    // 現在の状態を緊急保存
    const emergencyData = {
      timestamp: new Date().toISOString(),
      currentTask: this.getCurrentTask(),
      systemState: this.getSystemState(),
    };

    require('fs').writeFileSync(
      'emergency_state.json',
      JSON.stringify(emergencyData, null, 2)
    );
    console.log('緊急状態データを保存しました');
  }
}

自動停止条件の設定

特定の条件を満たした場合に自動的に停止する機能も重要です。

typescriptinterface AutoStopConditions {
  maxCPUUsage: number;
  maxMemoryUsage: number;
  maxDiskUsage: number;
  maxCommandCount: number;
  maxErrorCount: number;
}

class AutoStopManager {
  private conditions: AutoStopConditions;
  private errorCount: number = 0;
  private commandCount: number = 0;

  constructor(conditions: AutoStopConditions) {
    this.conditions = conditions;
  }

  checkStopConditions(): boolean {
    const checks = [
      this.checkCPUUsage(),
      this.checkMemoryUsage(),
      this.checkDiskUsage(),
      this.checkCommandCount(),
      this.checkErrorCount(),
    ];

    return checks.some((check) => check);
  }

  private checkCPUUsage(): boolean {
    // CPU使用率チェックの実装
    return false; // 実装に応じて変更
  }

  private checkMemoryUsage(): boolean {
    // メモリ使用率チェックの実装
    return false; // 実装に応じて変更
  }

  private checkCommandCount(): boolean {
    return (
      this.commandCount > this.conditions.maxCommandCount
    );
  }

  private checkErrorCount(): boolean {
    return this.errorCount > this.conditions.maxErrorCount;
  }

  incrementCommandCount(): void {
    this.commandCount++;
  }

  incrementErrorCount(): void {
    this.errorCount++;
  }
}

タイムアウト設定の最適化

適切なタイムアウト設定により、長時間実行される処理を制御できます。

処理別タイムアウトの設定

javascriptconst timeoutConfig = {
  fileRead: 30000, // ファイル読み込み: 30秒
  fileWrite: 60000, // ファイル書き込み: 1分
  codeGeneration: 120000, // コード生成: 2分
  testing: 300000, // テスト実行: 5分
  building: 600000, // ビルド処理: 10分
};

class TimeoutManager {
  constructor() {
    this.activeTimeouts = new Map();
  }

  executeWithTimeout(
    taskType,
    operation,
    customTimeout = null
  ) {
    const timeout =
      customTimeout || timeoutConfig[taskType] || 60000;
    const taskId = this.generateTaskId();

    return new Promise((resolve, reject) => {
      const timeoutId = setTimeout(() => {
        this.activeTimeouts.delete(taskId);
        reject(
          new Error(
            `タスク ${taskType} がタイムアウトしました (${timeout}ms)`
          )
        );
      }, timeout);

      this.activeTimeouts.set(taskId, timeoutId);

      Promise.resolve(operation())
        .then((result) => {
          clearTimeout(timeoutId);
          this.activeTimeouts.delete(taskId);
          resolve(result);
        })
        .catch((error) => {
          clearTimeout(timeoutId);
          this.activeTimeouts.delete(taskId);
          reject(error);
        });
    });
  }

  generateTaskId() {
    return `task_${Date.now()}_${Math.random()
      .toString(36)
      .substr(2, 9)}`;
  }

  clearAllTimeouts() {
    for (const [taskId, timeoutId] of this.activeTimeouts) {
      clearTimeout(timeoutId);
    }
    this.activeTimeouts.clear();
  }
}

段階的タイムアウトの実装

処理の重要度や複雑さに応じて、段階的にタイムアウトを調整する仕組みも有効です。

typescriptenum TaskPriority {
  LOW = 'low',
  MEDIUM = 'medium',
  HIGH = 'high',
  CRITICAL = 'critical',
}

interface TimeoutConfig {
  [TaskPriority.LOW]: number;
  [TaskPriority.MEDIUM]: number;
  [TaskPriority.HIGH]: number;
  [TaskPriority.CRITICAL]: number;
}

class AdaptiveTimeoutManager {
  private baseTimeouts: TimeoutConfig = {
    [TaskPriority.LOW]: 30000, // 30秒
    [TaskPriority.MEDIUM]: 120000, // 2分
    [TaskPriority.HIGH]: 300000, // 5分
    [TaskPriority.CRITICAL]: 600000, // 10分
  };

  getTimeoutForTask(
    priority: TaskPriority,
    complexity: number = 1
  ): number {
    const baseTimeout = this.baseTimeouts[priority];
    const complexityMultiplier = Math.min(complexity, 3); // 最大3倍まで
    return baseTimeout * complexityMultiplier;
  }

  adjustTimeoutBasedOnHistory(
    taskType: string,
    averageExecutionTime: number
  ): number {
    // 過去の実行時間の平均に基づいてタイムアウトを調整
    const buffer = 1.5; // 50%のバッファを追加
    return Math.ceil(averageExecutionTime * buffer);
  }
}

具体例

実際の無限ループ事例と対処法

実際に発生した無限ループの事例を通じて、問題の特定方法と対処法を詳しく解説します。

事例 1: ファイル探索の無限ループ

あるプロジェクトで、Cline が特定のファイルを探そうとした際に、シンボリックリンクによる循環参照が原因で無限ループに陥ったケースです。

問題の発生状況を以下の図で示します。

mermaidgraph TD
    start[探索開始] --> dir_a[ディレクトリA]
    dir_a --> symlink[シンボリックリンク]
    symlink --> dir_b[ディレクトリB]
    dir_b --> back_to_a[Aへの参照]
    back_to_a --> dir_a

    style symlink fill:#ff9999
    style back_to_a fill:#ff9999

このような循環参照を検出し、回避するための対策コードをご紹介します。

javascriptconst fs = require('fs');
const path = require('path');

class SafeFileExplorer {
  constructor() {
    this.visitedPaths = new Set();
    this.maxDepth = 10;
    this.maxFiles = 1000;
    this.fileCount = 0;
  }

  exploreDirectory(dirPath, currentDepth = 0) {
    // 深度制限チェック
    if (currentDepth > this.maxDepth) {
      console.warn(
        `最大深度 ${this.maxDepth} に達しました: ${dirPath}`
      );
      return [];
    }

    // ファイル数制限チェック
    if (this.fileCount > this.maxFiles) {
      console.warn(
        `最大ファイル数 ${this.maxFiles} に達しました`
      );
      return [];
    }

    // 循環参照チェック
    const realPath = fs.realpathSync(dirPath);
    if (this.visitedPaths.has(realPath)) {
      console.warn(
        `循環参照を検出しました: ${dirPath} -> ${realPath}`
      );
      return [];
    }

    this.visitedPaths.add(realPath);

    try {
      const items = fs.readdirSync(dirPath);
      const results = [];

      for (const item of items) {
        const itemPath = path.join(dirPath, item);
        const stats = fs.lstatSync(itemPath);

        if (
          stats.isDirectory() &&
          !stats.isSymbolicLink()
        ) {
          // 通常のディレクトリの場合は再帰探索
          results.push(
            ...this.exploreDirectory(
              itemPath,
              currentDepth + 1
            )
          );
        } else if (stats.isFile()) {
          results.push(itemPath);
          this.fileCount++;
        }
        // シンボリックリンクは安全性のため無視
      }

      return results;
    } catch (error) {
      console.error(
        `ディレクトリ探索エラー: ${dirPath}`,
        error.message
      );
      return [];
    } finally {
      this.visitedPaths.delete(realPath);
    }
  }
}

事例 2: API 呼び出しの無限ループ

外部 API からデータを取得する際に、エラーレスポンスに対する適切な処理がなく、リトライが無限に続いたケースです。

javascriptclass SafeAPIClient {
  constructor() {
    this.maxRetries = 3;
    this.baseDelay = 1000; // 1秒
    this.maxDelay = 30000; // 30秒
    this.failureHistory = new Map();
  }

  async makeRequest(url, options = {}, retryCount = 0) {
    // 連続失敗チェック
    if (this.shouldSkipRequest(url)) {
      throw new Error(
        `URL ${url} は連続失敗により一時的にブロックされています`
      );
    }

    try {
      const response = await fetch(url, {
        ...options,
        timeout: 10000, // 10秒タイムアウト
      });

      if (!response.ok) {
        throw new Error(`HTTP Error: ${response.status}`);
      }

      // 成功時は失敗履歴をクリア
      this.clearFailureHistory(url);
      return await response.json();
    } catch (error) {
      this.recordFailure(url);

      // リトライ判定
      if (
        retryCount < this.maxRetries &&
        this.shouldRetry(error)
      ) {
        const delay = this.calculateDelay(retryCount);
        console.log(
          `リトライします (${retryCount + 1}/${
            this.maxRetries
          }): ${delay}ms後`
        );

        await this.sleep(delay);
        return this.makeRequest(
          url,
          options,
          retryCount + 1
        );
      }

      throw error;
    }
  }

  shouldSkipRequest(url) {
    const failures = this.failureHistory.get(url);
    if (!failures) return false;

    const recentFailures = failures.filter(
      (time) => Date.now() - time < 300000 // 5分以内
    );

    return recentFailures.length >= 5; // 5回連続失敗でブロック
  }

  recordFailure(url) {
    if (!this.failureHistory.has(url)) {
      this.failureHistory.set(url, []);
    }
    this.failureHistory.get(url).push(Date.now());
  }

  clearFailureHistory(url) {
    this.failureHistory.delete(url);
  }

  shouldRetry(error) {
    // ネットワークエラーやタイムアウトの場合はリトライ
    return (
      error.code === 'TIMEOUT' ||
      error.code === 'ECONNRESET' ||
      error.message.includes('network')
    );
  }

  calculateDelay(retryCount) {
    // 指数バックオフ
    const delay = this.baseDelay * Math.pow(2, retryCount);
    return Math.min(delay, this.maxDelay);
  }

  sleep(ms) {
    return new Promise((resolve) =>
      setTimeout(resolve, ms)
    );
  }
}

安全装置の設定コード例

実際に使用できる包括的な安全装置システムの実装例をご紹介します。

統合安全装置システム

typescriptimport { EventEmitter } from 'events';

interface SafetyConfig {
  maxThinkingSteps: number;
  maxExecutionTime: number;
  maxMemoryUsage: number;
  maxCPUUsage: number;
  maxCommandsPerMinute: number;
  emergencyStopEnabled: boolean;
}

class ClineSafetySystem extends EventEmitter {
  private config: SafetyConfig;
  private thinkingCount: number = 0;
  private startTime: number = 0;
  private commandMonitor: CommandMonitor;
  private resourceMonitor: ResourceMonitor;
  private emergencyStop: EmergencyStop;
  private isActive: boolean = false;

  constructor(config: SafetyConfig) {
    super();
    this.config = config;
    this.commandMonitor = new CommandMonitor();
    this.resourceMonitor = new ResourceMonitor();
    this.emergencyStop = new EmergencyStop();

    this.setupEventListeners();
  }

  start(): void {
    this.isActive = true;
    this.startTime = Date.now();
    this.thinkingCount = 0;

    // 各監視システムを開始
    this.resourceMonitor.startMonitoring();

    // 定期的な安全チェック
    this.schedulePeriodicChecks();

    this.emit('safety-system-started');
    console.log('🛡️ Cline安全装置システムが起動しました');
  }

  stop(): void {
    this.isActive = false;
    this.resourceMonitor.stopMonitoring();
    this.emit('safety-system-stopped');
    console.log('🛡️ Cline安全装置システムが停止しました');
  }

  recordThinking(): boolean {
    if (!this.isActive) return true;

    this.thinkingCount++;

    if (
      this.thinkingCount >= this.config.maxThinkingSteps
    ) {
      this.triggerSafetyStop('thinking-limit-exceeded', {
        count: this.thinkingCount,
        limit: this.config.maxThinkingSteps,
      });
      return false;
    }

    return true;
  }

  recordCommand(command: string): boolean {
    if (!this.isActive) return true;

    const result =
      this.commandMonitor.recordCommand(command);

    if (result.action === 'block') {
      this.triggerSafetyStop('command-blocked', {
        command,
        reason: result.message,
      });
      return false;
    } else if (result.action === 'warn') {
      this.emit('safety-warning', {
        type: 'command-frequency',
        message: result.message,
      });
    }

    return true;
  }

  checkExecutionTime(): boolean {
    if (!this.isActive) return true;

    const elapsed = Date.now() - this.startTime;

    if (elapsed >= this.config.maxExecutionTime) {
      this.triggerSafetyStop('execution-timeout', {
        elapsed,
        limit: this.config.maxExecutionTime,
      });
      return false;
    }

    return true;
  }

  private setupEventListeners(): void {
    this.resourceMonitor.on('high-cpu', (usage) => {
      if (usage > this.config.maxCPUUsage) {
        this.triggerSafetyStop('cpu-limit-exceeded', {
          usage,
          limit: this.config.maxCPUUsage,
        });
      }
    });

    this.resourceMonitor.on('high-memory', (usage) => {
      if (usage > this.config.maxMemoryUsage) {
        this.triggerSafetyStop('memory-limit-exceeded', {
          usage,
          limit: this.config.maxMemoryUsage,
        });
      }
    });

    this.emergencyStop.on(
      'emergency-triggered',
      (reason) => {
        this.triggerSafetyStop('emergency-stop', {
          reason,
        });
      }
    );
  }

  private schedulePeriodicChecks(): void {
    const checkInterval = setInterval(() => {
      if (!this.isActive) {
        clearInterval(checkInterval);
        return;
      }

      this.performPeriodicChecks();
    }, 5000); // 5秒ごと
  }

  private performPeriodicChecks(): void {
    this.checkExecutionTime();
    // その他の定期チェック項目
  }

  private triggerSafetyStop(
    reason: string,
    details: any
  ): void {
    console.error(`🚨 安全装置発動: ${reason}`, details);

    this.emit('safety-triggered', { reason, details });

    if (this.config.emergencyStopEnabled) {
      this.performEmergencyStop(reason, details);
    }
  }

  private performEmergencyStop(
    reason: string,
    details: any
  ): void {
    // 緊急停止処理
    this.stop();

    // 状態を保存
    this.saveEmergencyState(reason, details);

    // プロセス終了
    process.exit(1);
  }

  private saveEmergencyState(
    reason: string,
    details: any
  ): void {
    const emergencyData = {
      timestamp: new Date().toISOString(),
      reason,
      details,
      thinkingCount: this.thinkingCount,
      executionTime: Date.now() - this.startTime,
      systemInfo: {
        platform: process.platform,
        nodeVersion: process.version,
        memoryUsage: process.memoryUsage(),
      },
    };

    require('fs').writeFileSync(
      `emergency-state-${Date.now()}.json`,
      JSON.stringify(emergencyData, null, 2)
    );
  }
}

設定ファイルの例

json{
  "safety": {
    "maxThinkingSteps": 50,
    "maxExecutionTime": 300000,
    "maxMemoryUsage": 85,
    "maxCPUUsage": 80,
    "maxCommandsPerMinute": 30,
    "emergencyStopEnabled": true
  },
  "monitoring": {
    "resourceCheckInterval": 5000,
    "commandHistoryWindow": 60000,
    "logLevel": "info"
  },
  "timeouts": {
    "fileOperation": 30000,
    "networkRequest": 10000,
    "codeGeneration": 120000
  }
}

モニタリング環境の構築

安全装置の効果を最大化するためには、適切なモニタリング環境の構築が重要です。

ダッシュボード機能

javascriptconst express = require('express');
const app = express();

class SafetyDashboard {
  constructor(safetySystem) {
    this.safetySystem = safetySystem;
    this.metrics = {
      thinkingCount: 0,
      commandCount: 0,
      warnings: [],
      alerts: [],
    };

    this.setupRoutes();
    this.setupEventListeners();
  }

  setupRoutes() {
    app.get('/api/status', (req, res) => {
      res.json({
        active: this.safetySystem.isActive,
        metrics: this.metrics,
        timestamp: new Date().toISOString(),
      });
    });

    app.get('/api/metrics', (req, res) => {
      res.json(this.getDetailedMetrics());
    });

    app.post('/api/emergency-stop', (req, res) => {
      this.safetySystem.triggerEmergencyStop('manual');
      res.json({ status: 'emergency stop triggered' });
    });

    app.use(express.static('dashboard'));
  }

  setupEventListeners() {
    this.safetySystem.on('safety-warning', (warning) => {
      this.metrics.warnings.push({
        ...warning,
        timestamp: new Date().toISOString(),
      });

      // 最新100件のみ保持
      if (this.metrics.warnings.length > 100) {
        this.metrics.warnings =
          this.metrics.warnings.slice(-100);
      }
    });

    this.safetySystem.on('safety-triggered', (alert) => {
      this.metrics.alerts.push({
        ...alert,
        timestamp: new Date().toISOString(),
      });
    });
  }

  getDetailedMetrics() {
    return {
      thinking: {
        count: this.metrics.thinkingCount,
        rate: this.calculateThinkingRate(),
      },
      commands: {
        count: this.metrics.commandCount,
        rate: this.calculateCommandRate(),
      },
      system: {
        cpu: this.getCurrentCPUUsage(),
        memory: this.getCurrentMemoryUsage(),
        uptime: process.uptime(),
      },
      warnings: this.metrics.warnings.slice(-10), // 最新10件
      alerts: this.metrics.alerts.slice(-5), // 最新5件
    };
  }

  start(port = 3001) {
    app.listen(port, () => {
      console.log(
        `📊 安全装置ダッシュボードが起動しました: http://localhost:${port}`
      );
    });
  }
}

ログ出力システム

typescriptimport winston from 'winston';

class SafetyLogger {
  private logger: winston.Logger;

  constructor() {
    this.logger = winston.createLogger({
      level: 'info',
      format: winston.format.combine(
        winston.format.timestamp(),
        winston.format.errors({ stack: true }),
        winston.format.json()
      ),
      transports: [
        new winston.transports.File({
          filename: 'safety-error.log',
          level: 'error',
        }),
        new winston.transports.File({
          filename: 'safety-combined.log',
        }),
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.colorize(),
            winston.format.simple()
          ),
        }),
      ],
    });
  }

  logThinking(count: number, limit: number): void {
    this.logger.info('思考ステップ記録', {
      count,
      limit,
      percentage: (count / limit) * 100,
    });
  }

  logCommand(command: string, status: string): void {
    this.logger.info('コマンド実行', { command, status });
  }

  logWarning(
    type: string,
    message: string,
    details?: any
  ): void {
    this.logger.warn('安全装置警告', {
      type,
      message,
      details,
    });
  }

  logEmergencyStop(reason: string, details: any): void {
    this.logger.error('緊急停止発動', { reason, details });
  }

  logResourceUsage(cpu: number, memory: number): void {
    this.logger.info('リソース使用状況', { cpu, memory });
  }
}

図で理解できる要点:

  • 安全装置システムは多層防御により包括的な保護を提供
  • 監視システムはリアルタイムでの異常検出を可能にする
  • ダッシュボードにより視覚的な状況把握と即座の対応が実現される

まとめ

Cline の無限思考ループと暴走コマンドに対する安全装置の構築は、安全で効率的な開発環境を維持するために不可欠です。本記事で紹介した対策を適切に実装することで、これらの問題を効果的に予防し、発生時には迅速に対処することができるでしょう。

安全装置設定のベストプラクティス

効果的な安全装置を構築するためには、以下のベストプラクティスを遵守することが重要です。

まず、段階的な制限設定を心がけましょう。一度にすべての制限を厳しく設定するのではなく、まずは基本的な制限から開始し、運用状況を見ながら段階的に調整していくことが大切です。これにより、開発効率を維持しながら安全性を向上させることができます。

次に、複数の監視指標の組み合わせが効果的です。単一の指標だけでなく、思考回数、実行時間、リソース使用量、コマンド頻度など、複数の指標を組み合わせることで、より正確な異常検出が可能になります。

また、アラートの優先度設定も重要な要素です。すべての異常を同じレベルで扱うのではなく、システムに与える影響の大きさに応じて優先度を設定し、対応の緊急度を明確にしましょう。

ログ記録の充実により、問題発生時の原因特定と再発防止が容易になります。単にエラーを記録するだけでなく、実行コンテキストや環境情報も含めて記録することで、より効果的なトラブルシューティングが可能になります。

継続的な監視の重要性

安全装置は一度設定すれば終わりではありません。継続的な監視と改善が、長期的な安全性確保のカギとなります。

定期的な設定見直しを実施し、プロジェクトの成長や要件の変化に応じて制限値や監視項目を調整してください。開発チームの規模が大きくなったり、扱うデータの量が増加したりした場合には、それに応じた設定変更が必要になります。

パフォーマンス影響の評価も忘れずに行いましょう。安全装置自体が開発作業の妨げになっては本末転倒です。監視システムのオーバーヘッドを定期的に測定し、必要に応じて最適化を行ってください。

チーム全体での知識共有により、安全装置の効果を最大化できます。設定内容や運用ルールをドキュメント化し、チームメンバー全員が理解できるようにしておくことが重要です。

最後に、緊急時対応手順の整備を怠らないでください。実際に問題が発生した際に、迅速かつ適切に対応できるよう、事前に手順を定めておき、定期的な訓練を実施することをお勧めします。

適切な安全装置の実装により、Cline の強力な機能を安心して活用できる開発環境を構築することができます。本記事で紹介した手法を参考に、皆様の開発プロジェクトに最適な安全装置システムを構築していただければと思います。

関連リンク