Skip to content

スケジュール実行の登録 #294

@Kewton

Description

@Kewton

Note: このIssueは 2026-02-23 にレビュー結果(Stage 1, Stage 3, Stage 5, Stage 7)を反映して更新されました。
詳細: dev-reports/issue/294/issue-review/

Update (2026-02-23): claude -p フラグの事前検証を実施済み。フラグの存在・JSON出力・ツール自動承認等を確認。CLAUDECODE 環境変数除去の必要性を発見し、設計に反映。

Update (2026-02-23): マルチステージ設計レビュー(4段階: 設計原則→整合性→影響分析→セキュリティ)を実施済み。設計方針書(dev-reports/design/issue-294-schedule-execution-design-policy.md)を作成し、Must Fix 8件 / Should Fix 20件の指摘事項を反映。以下の主要変更をIssue本文に反映:

  • 新規ファイル追加: env-sanitizer.tscmate-sync.ts(条件付き)、execution-logs/[logId]/route.ts
  • セキュリティ強化: SENSITIVE_ENV_KEYS による機密環境変数の包括的除去、Message列/Name列のUnicode制御文字サニタイズ
  • 影響分析: graceful shutdown 3秒以内のSIGKILL停止保証、v17マイグレーション孤児レコードクリーンアップ
  • 詳細: dev-reports/issue/294/multi-stage-design-review/summary-report.md

概要

Worktree(セッション)に対して、指定した日時やスケジュールでCLIメッセージを自動送信する「スケジュール実行」機能を追加する。各worktreeディレクトリのルートに配置する CMATE.md(Markdownテーブル形式)でスケジュールと指示を定義し、claude -p(print mode)で新規プロセスとして実行する。実行結果ログはUI上で確認可能にする。

背景・課題

  • 現在、CommandMateでのCLI操作はすべて手動トリガーまたはAuto-Yes(プロンプト自動応答)のみ
  • 定期的に同じコマンドを実行したいユースケース(日次ビルド、定期テスト、レポート生成など)に対応できない
  • Auto-Yes機能(auto-yes-manager.ts)は「プロンプトへの自動応答」であり、「メッセージの自動送信」とは異なる
  • ユーザーが不在時にも定型タスクを実行させたいニーズがある

設計方針(インタビュー確認済み)

1. CMATE.md: CommandMate汎用設定ファイル

CMATE.mdはスケジュール実行専用ではなく、CommandMateの汎用設定ファイルとして位置づける。## 見出しでセクションを区切り、各セクションがそれぞれの機能に対応する拡張可能な構造とする。

  • 配置: 各worktreeディレクトリのルートに配置する(worktree単位の設定ファイル)
  • フォーマット: Markdown見出しセクション形式(## セクション名 + テーブル)
  • パーサー設計: セクション単位で独立してパースし、未知のセクションは無視する(前方互換性)
  • ファイル変更検知: ポーリング方式(60秒間隔で stat().mtime を比較)を使用する
  • cronライブラリ: croner を使用(TypeScript対応、軽量、次回実行時刻計算API提供)
  • CLAUDE.mdとの区別: CMATE.mdはCommandMateの設定ファイルであり、CLAUDE.md(Claude Code設定)とは別物。UIやドキュメントで混同しないよう注記する
  • Git管理方針: CMATE.mdはworktree単位の個人設定ファイルとして位置づけ、.gitignore への追加を推奨する。commandmate init 時に .gitignoreCMATE.md を追加する処理を検討する。ただし、チーム共有のスケジュール設定を行いたい場合はGit管理対象としてもよい
# CMATE.md

## Schedules

| Name | Cron | Message |
|------|------|---------|
| 朝のレビュー | 0 9 * * * | レビューして |
| 夕方テスト | 0 17 * * * | npm test を実行して |

## Queues(将来対応)

| Order | Message |
|-------|---------|
| 1 | レビューして |
| 2 | 修正して |

## Settings(将来対応)

| Key | Value |
|-----|-------|
| auto-yes-duration | 30 |

本Issueのスコープは ## Schedules セクションの実装のみ。パーサーは汎用的に作り、将来のセクション追加に備える。

2. スケジュール登録方式: CMATE.md + DB 両方対応

  • 定義元: CMATE.md を手動編集して定義
  • DB同期: サーバーが CMATE.md を読み込み、scheduled_executions テーブルに同期
  • upsert戦略: worktree_id + name で既存レコードを検索し、存在すればUPDATE、なければINSERT。CMATE.mdから消えたレコードは enabled を false に設定する

3. 実行方式: claude -p 新規プロセス

  • スケジュール実行時は 既存のtmuxセッションとは別に claude -p(print mode / 非対話モード)を新規プロセスとして起動
  • 実行結果はログとしてDBに保存
  • 検証済み: claude -p フラグの存在・挙動は事前検証で確認済み(詳細は実装タスクの事前検証セクションを参照)。CLAUDECODE 環境変数の除去が必要

既存セッションとの並行動作に関する注意事項:

  • claude -p は独立プロセスであり tmux セッション経由ではないため、auto-yes-manager.ts のポーリングには影響しない
  • ただし、claude -p がworktreeのファイルを変更する可能性があるため、対話的セッションで作業中のユーザーへの注意事項をUIまたはドキュメントに記載する
  • .claude/ ディレクトリの同時アクセスについては、Claude CLI側の排他制御に依存する

4. UI: Memoタブのリネーム・拡張

  • 既存の Memoタブをリネームし、「メモ+実行ログ」を含む形に拡張
  • 拡張方式: 新規コンテナコンポーネント(NotesAndLogsPane.tsx 等)を作成し、MemoPane と ExecutionLogPane をラップするサブタブ構造(方針B)を採用する
  • tab ID: 'memo' のまま維持し、表示ラベルのみ変更する(LocalStorage互換性を確保)
  • サブタブ状態管理: NotesAndLogsPane 内部で useState によりサブタブ状態を管理し、WorktreeDetailRefactored.tsx にはサブタブ状態を一切漏洩させない。サブタブの初期値は 'memo'(既存動作互換)とし、LocalStorage へのサブタブ永続化は初期実装では行わない
  • スマホからも実行ログを確認可能

5. #292(命令行列)との関係: 完全に独立

提案する解決策

アーキテクチャ

既存のauto-yes-manager.tsのパターン(globalThis + setTimeout再帰ループ)を踏襲し、サーバーサイドスケジューラーを実装する。

globalThis変数の命名規約: 既存の __autoYesStates / __autoYesPollerStates / __versionCheckCache パターンに準拠し、以下の変数名を使用する:

  • globalThis.__scheduleManagerStates - スケジューラーの状態(タイマーID等)を管理
  • globalThis.__scheduleActiveProcesses - 実行中の子プロセスリストを管理

注意: declare global ブロックで型定義を追加すること。

cronライブラリは croner を使用する(TypeScript対応、軽量、次回実行時刻計算APIを提供)。

スケジューラー初期化の呼び出し元: server.ts(カスタムサーバー)のサーバー起動完了後に initScheduleManager() を呼び出す。既存の auto-yes-manager.ts がAPIルート経由でオンデマンド開始されるのとは異なり、スケジューラーはサーバー起動時に自動開始する必要があるため、カスタムサーバーからの明示的な初期化が必要。

主要な変更点

  1. 環境変数サニタイズユーティリティ: env-sanitizer.tsを新規作成。SENSITIVE_ENV_KEYS配列(CLAUDECODE, CM_AUTH_TOKEN_HASH, CM_HTTPS_KEY等)による機密環境変数の一元除去(設計レビューS1-001/S4-001対応)
  2. CMATE.mdパーサー(汎用): Markdown見出しセクション単位でテーブルをパースする汎用パーサー。parseCmateFile()Map<string, string[][]> を返す。## Schedules セクションを本Issueで実装し、他セクションは将来のIssueで対応(設計レビューS1-010対応: 汎用設計範囲の限定)
  3. DBマイグレーション(v17): scheduled_executions + execution_logs テーブルを追加。既存孤児レコードのクリーンアップSQLを含む(設計レビューS3-002対応)
  4. サーバーサイドスケジューラー: schedule-manager.tsを新規作成、globalThisでタイマー状態を管理。500行超の場合はcmate-sync.tsに分離(設計レビューS1-002対応)
  5. claude -p 実行エンジン: 非対話モードでの新規プロセス起動・結果取得(セキュリティ対策・タイムアウト制御・SENSITIVE_ENV_KEYS除去込み)。MAX_OUTPUT_SIZE(1MB)とMAX_STORED_OUTPUT_SIZE(100KB)の2段階サイズ制限(設計レビューS1-014対応)
  6. PRAGMA foreign_keys = ON の有効化: db-instance.tsgetDbInstance()dbInstance.pragma('foreign_keys = ON')runMigrations()に追加し、CASCADE削除を正しく機能させる(設計レビューS2-010対応)
  7. APIルート: スケジュールのCRUD操作 + 実行ログ取得用エンドポイント(一覧API: resultカラム除外、個別取得API: result含む。設計レビューS1-014/S2-002対応)
  8. UIコンポーネント: 新規コンテナコンポーネントによるMemoタブ拡張(メモ+実行ログ表示)。NotesAndLogsPane props: { worktreeId: string; className?: string; }(設計レビューS1-013対応)
  9. 入力サニタイズ: Message列のUnicode制御文字ストリッピング、Name列のNAME_PATTERNバリデーション(設計レビューS4-002/S4-011対応)

DBスキーマ案

CREATE TABLE scheduled_executions (
  id TEXT PRIMARY KEY,              -- randomUUID() で生成
  worktree_id TEXT NOT NULL,
  cli_tool_id TEXT NOT NULL DEFAULT 'claude',
  name TEXT NOT NULL,
  message TEXT NOT NULL,
  cron_expression TEXT,
  enabled INTEGER DEFAULT 1,
  last_executed_at INTEGER,
  next_execute_at INTEGER,
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL,
  FOREIGN KEY (worktree_id) REFERENCES worktrees(id) ON DELETE CASCADE,
  UNIQUE (worktree_id, name)        -- CMATE.md同期のupsertキー
);

-- インデックス: 有効スケジュールの次回実行時刻取得を高速化
CREATE INDEX idx_scheduled_executions_next ON scheduled_executions(enabled, next_execute_at);

CREATE TABLE execution_logs (
  id TEXT PRIMARY KEY,              -- randomUUID() で生成
  schedule_id TEXT NOT NULL,
  worktree_id TEXT NOT NULL,
  message TEXT NOT NULL,
  result TEXT,
  exit_code INTEGER,
  status TEXT NOT NULL DEFAULT 'running' CHECK(status IN ('running', 'completed', 'failed', 'timeout', 'cancelled')),
  started_at INTEGER NOT NULL,
  completed_at INTEGER,
  created_at INTEGER NOT NULL,
  FOREIGN KEY (schedule_id) REFERENCES scheduled_executions(id) ON DELETE CASCADE,
  FOREIGN KEY (worktree_id) REFERENCES worktrees(id) ON DELETE CASCADE
);

-- インデックス: ログ検索のパフォーマンス最適化
CREATE INDEX idx_execution_logs_worktree ON execution_logs(worktree_id, created_at DESC);
CREATE INDEX idx_execution_logs_schedule ON execution_logs(schedule_id, created_at DESC);

ID生成方式: randomUUID()import { randomUUID } from 'crypto')を使用(既存の db.ts パターンに準拠)。

PRAGMA foreign_keys = ON の前提条件: SQLiteはデフォルトで foreign_keys = OFF であり、ON DELETE CASCADE が機能しない。本Issueの実装タスクとして db-instance.tsgetDbInstance()dbInstance.pragma('foreign_keys = ON') を追加する。これにより、本Issue の CASCADE 削除だけでなく、既存テーブル(chat_messages, session_states, worktree_memos 等)の CASCADE 削除も正しく機能するようになる。既存テストへの影響を確認すること。

CASCADE削除に関する注意: execution_logsschedule_idworktree_id の両方に対して ON DELETE CASCADE を設定している。worktree削除時には scheduled_executions がCASCADE削除され、それに伴い execution_logs もCASCADE削除される(二重CASCADE削除パス)。この動作を検証するテストケースを実装タスクに含めること。

cli_tool_id カラムについて: cli_tool_id は将来の他CLIツール対応(codex, gemini等)に備えた予約カラム。本Issueではデフォルト値 'claude' のみを使用し、CMATE.mdの Schedules テーブルには CLI Tool 列を追加しない。

scheduled_at / repeat_interval_ms カラムの除外(YAGNI原則): 初期設計案に含まれていた scheduled_at INTEGER(一回限り実行用)と repeat_interval_ms INTEGER(cron以外の間隔指定用)は、本Issueのスコープで使用しないため初期実装のスキーマから除外する。必要になった時点で将来のマイグレーションで追加する。

CMATE.md同期のupsert戦略:

  1. worktree_id + name で既存レコードを検索(UNIQUE制約で一意性を保証)
  2. 存在すれば UPDATE(cron_expression, message 等を更新)
  3. なければ INSERT(新規レコード作成)
  4. CMATE.md から消えたレコードは enabledfalse に設定(削除ではなくソフト無効化)

将来検討: MAX_EXECUTION_LOGS_PER_SCHEDULE 定数(例: 100件)による古いログの自動クリーンアップ

実装タスク

事前検証(2026-02-23 実施済み)

  • claude -p フラグの動作検証: 検証完了。claude -p による非対話実行がTypeScriptサーバーからの child_process 経由で利用可能であることを確認。

参考ドキュメント: https://code.claude.com/docs/en/headless#run-claude-code-programmatically

検証環境: Claude Code v2.1.50

検証結果サマリ:

検証項目 結果
フラグ存在 -p / --print 両方利用可能(claude --help で確認済み)
出力フォーマット --output-formattext(デフォルト)/ json / stream-json を選択可能
JSON出力 result, session_id 等のメタデータを含む構造化出力。--json-schema で出力スキーマ指定も可能
ツール自動承認 --allowedToolsBash,Read,Edit 等のツールを自動承認可能
会話継続 --continue(直前の会話)/ --resume <session_id>(特定の会話)で継続可能
予算制限 --max-budget-usd でAPI費用の上限設定可能(-p モード専用)
システムプロンプト --append-system-prompt(追記)/ --system-prompt(全置換)で制御可能
cwd制御 worktreeディレクトリで実行すれば、そのコンテキストで動作する
セッション永続化無効化 --no-session-persistence でセッション保存を無効化可能(-p モード専用)
フォールバックモデル --fallback-model でオーバーロード時の代替モデル指定可能(-p モード専用)

重要な発見事項 - ネスト実行制限:

  • CommandMateサーバープロセス内から claude -p を実行すると、CLAUDECODE 環境変数の存在によりネスト実行としてブロックされる
  • 回避策: child_process 起動時に環境変数から CLAUDECODE を除去する必要がある
  • これは既存の claude-session.tssanitizeSessionEnvironment() と同じパターンであり、claude-executor.ts の実装時に同様の対策が必須
// claude-executor.ts での実装イメージ
const env = { ...process.env };
delete env.CLAUDECODE; // ネスト実行制限を回避

execFile('claude', ['-p', message, '--output-format', 'json'], {
  cwd: worktreePath,
  env,
  timeout: EXECUTION_TIMEOUT_MS,
  maxBuffer: MAX_OUTPUT_SIZE,
}, callback);

代替案(不要と判断): tmuxセッション経由での実行は不要。claude -p が正常に利用可能であることを確認済み。

共通ユーティリティ(設計レビューS1-001/S4-001対応)

  • src/lib/env-sanitizer.ts 新規作成(SENSITIVE_ENV_KEYS配列 + sanitizeEnvForChildProcess()関数)
    • 除去対象: CLAUDECODE, CM_AUTH_TOKEN_HASH, CM_AUTH_EXPIRE, CM_HTTPS_KEY, CM_HTTPS_CERT, CM_ALLOWED_IPS, CM_TRUST_PROXY, CM_DB_PATH
    • claude-executor.ts から利用(主要ユースケース)
    • claude-session.ts への適用は createSession() にenv引数がある場合のみ(S2-011: tmux経由unsetとは別)
  • env-sanitizer.test.ts 作成(全SENSITIVE_ENV_KEYS変数の除去テスト、非対象変数の保持テスト)

CMATE.mdパーサー(汎用設計)

  • src/lib/cmate-parser.ts 新規作成(Markdown見出しセクション + テーブルの汎用パーサー)
    • parseCmateFile(): Map<string, string[][]> を返す汎用パーサー(設計レビューS1-010対応: 汎用設計範囲を明確に限定)
    • parseSchedulesSection(): ScheduleEntry[] を返す型変換関数
    • セクション名をキーとしたMap形式で返す設計
    • 未知のセクションは無視(前方互換性)
    • 本Issueでは ## Schedules セクションのみ実装
    • エラーハンドリング: 個別エントリのパースエラーは警告ログを出力し当該エントリのみスキップ、ファイル不存在時は空として扱う
    • パストラバーサル防御: ファイルパス構築時に realpath() でシンボリックリンクを解決し、worktreeディレクトリ内であることを検証する(SEC-xxx: file-operations.ts のSEC-005〜009パターン準拠)
    • cron式バリデーション: MIN_CRON_INTERVAL 定数(最小間隔1分)を設け、それ未満の頻度のcron式を拒否する。MAX_CRON_EXPRESSION_LENGTH 定数(100文字)による長さ制限を適用する
    • worktreeディレクトリパス取得: DBの worktrees テーブルから worktree の path カラムを取得し、path.join(worktreePath, 'CMATE.md') でファイルパスを構築する
    • Message列のUnicode制御文字サニタイズ(設計レビューS4-002対応): sanitizeMessageContent() 関数でC0/C1制御文字、ゼロ幅文字、方向制御文字をストリッピング(タブ・改行は許可)
    • Name列バリデーション(設計レビューS4-011対応): NAME_PATTERN正規表現(ASCII英数字・日本語文字・スペース・ハイフン、1〜100文字)でバリデーション。不正な場合は当該エントリをスキップし警告ログ出力
  • src/types/cmate.ts 新規作成(CMATE.md の型定義。CmateConfig, ScheduleEntry 等)
  • CMATE.md → DB 同期ロジック(サーバー起動時 + ポーリング方式: 60秒間隔で stat().mtime を比較して変更検知)

DB・バックエンド

  • PRAGMA foreign_keys = ON の有効化: src/lib/db-instance.tsgetDbInstance()dbInstance.pragma('foreign_keys = ON') を追加する。SQLiteはデフォルトで foreign_keys = OFF であり、この設定がないと ON DELETE CASCADE が機能しない。既存テストで PRAGMA 変更による影響がないか確認すること
  • src/lib/db-migrations.ts にv17マイグレーション追加(scheduled_executions + execution_logsテーブル + インデックス)
    • 既存孤児レコードのクリーンアップSQLをテーブル作成のに実行(S3-002対応: PRAGMA有効化前に蓄積した孤児を除去)
    • validateSchema()requiredTables 配列に 'scheduled_executions''execution_logs' を追加
  • cronライブラリ(croner)の導入: npm install croner でインストールし、cron式パース・次回実行時刻計算に使用
  • src/lib/schedule-manager.ts 新規作成(globalThis + setTimeoutパターン)
    • globalThis変数名: globalThis.__scheduleManagerStates(タイマー状態管理)、globalThis.__scheduleActiveProcesses(実行中プロセス管理)。declare global ブロックで型定義すること
    • DoS防御: MAX_CONCURRENT_SCHEDULES 定数(例: 100)を追加し、全worktree合計のスケジュール数を制限する
    • タイマー設計推奨: worktreeごとに独立したsetTimeout/setIntervalではなく、単一のタイマーで全worktreeのCMATE.mdを巡回チェックする方式を推奨(タイマー数削減)
    • プロセスクリーンアップ: サーバー終了時の子プロセスcleanup(globalThisにactiveProcessesを保持)
    • 同一スケジュールの同時実行防止: 前回実行が完了していない場合はスキップ
    • 再起動リカバリ: サーバー起動時に execution_logsstatus='running' レコードを status='failed'(reason: server_restart)に更新するリカバリロジックを実装
    • 初期化呼び出し元: server.ts(カスタムサーバー)のサーバー起動完了後に initScheduleManager() を呼び出す。auto-yes-manager.ts(APIルート経由オンデマンド開始)とは異なり、サーバー起動時の自動開始が必要
    • 停止関数のエクスポート: stopAllSchedules() エクスポート関数を作成する。タイマーのキャンセルと実行中子プロセスの同期的 process.kill(pid, 'SIGKILL') fire-and-forget方式によるkillを行い、server.tsgracefulShutdown() の3秒タイムアウト内で確実に完了させる(設計レビューS3-001対応)
    • 責務分割: 500行超の場合は src/lib/cmate-sync.ts にCMATE.md変更検知・DB同期ロジックを分離する(設計レビューS1-002対応)
    • 初期化順序: initScheduleManager()await initializeWorktrees()完了後に呼び出すこと(設計レビューS3-010対応)
  • src/lib/claude-executor.ts 新規作成(claude -p プロセス起動・結果取得)
    • タイムアウト制御: EXECUTION_TIMEOUT_MS 定数(例: 5分)と AbortController/kill信号による強制終了
    • cwdオプション: child_processcwd オプションに worktree の path を設定する。cwdパスはDBの worktrees テーブルから取得した信頼済みソースを使用すること(process.cwd() を使用しないこと)
    • 出力サイズ制限: MAX_OUTPUT_SIZE 定数(例: 1MB)による出力サイズ制限。execFile を使用する場合は maxBuffer オプションを明示的に設定する。spawn を使用する場合はデータイベントでバイト数を累算し制限値で読み取りを停止する。制限を超えた場合は末尾をtruncateし「...(truncated)」を付与する
    • 環境変数サニタイズ: env-sanitizer.tssanitizeEnvForChildProcess() を使用し、SENSITIVE_ENV_KEYS配列に定義された機密環境変数(CLAUDECODE, CM_AUTH_TOKEN_HASH, CM_HTTPS_KEY等)を子プロセスから除去する(設計レビューS1-001/S4-001対応)
    • DB保存用出力サイズ制限: MAX_STORED_OUTPUT_SIZE(100KB)定数でDB保存前に切り詰め。MAX_OUTPUT_SIZE(1MB)はexecFile用バッファ上限として分離(設計レビューS1-014対応)
    • セキュリティ要件:
      • child_process.execFile または spawn を使用しシェル解釈を回避(SEC-001: 既存 git-utils.ts パターン準拠)
      • MAX_MESSAGE_LENGTH 定数(例: 10000文字)によるメッセージ長制限
      • 実行結果のDB保存前に stripAnsi() を適用
      • Name列の文字種バリデーション
      • MIN_CRON_INTERVAL / MAX_CRON_EXPRESSION_LENGTH によるcron式バリデーション(cmate-parser.ts側で適用)
  • src/app/api/worktrees/[id]/schedules/route.ts 新規作成(GET/POST)
  • src/app/api/worktrees/[id]/schedules/[scheduleId]/route.ts 新規作成(GET/PUT/DELETE)
  • src/app/api/worktrees/[id]/execution-logs/route.ts 新規作成(GET: 一覧取得、resultカラム除外。設計レビューS1-014対応)
  • src/app/api/worktrees/[id]/execution-logs/[logId]/route.ts 新規作成(GET: 個別取得、resultカラム含む。設計レビューS2-002対応)
  • 全新規APIルートに isValidWorktreeId() + getWorktreeById() の2段階worktree IDバリデーションを適用(設計レビューS4-010対応)
  • scheduleId / logId パラメータにUUIDv4フォーマットバリデーション適用(設計レビューS4-014対応)

UI

  • 新規コンテナコンポーネント(src/components/worktree/NotesAndLogsPane.tsx 等)を作成し、MemoPane と ExecutionLogPane をラップするサブタブ構造を実装
    • tab ID 'memo' は維持し、表示ラベルのみ変更(LocalStorage互換性確保)
    • MobileTabBar.tsx の TABS配列の label 値と LeftPaneTabSwitcher.tsx のタブラベルのみ変更
    • サブタブ状態は NotesAndLogsPane 内部で useState により管理し、WorktreeDetailRefactored.tsx にはサブタブ状態を漏洩させない。サブタブ初期値は 'memo'(既存動作互換)
  • src/components/worktree/ExecutionLogPane.tsx 新規作成(実行ログ一覧・詳細表示)
  • スケジュール管理UI: ExecutionLogPane 内にスケジュール一覧セクションを設け、各スケジュールの有効/無効トグル・次回実行予定・最終実行結果を表示する。スケジュールの作成・編集はCMATE.mdの直接編集で行い、UIからの作成・編集は初期実装のスコープ外とする

その他

  • スケジューラー初期化: server.ts(カスタムサーバー)のサーバー起動完了後に initScheduleManager() を呼び出し、CMATE.md読み込み → DB同期 → タイマー設定を実行する
  • ユニットテスト追加(詳細はテスト計画セクションを参照)
  • i18n対応(ja/en): schedule.json 名前空間を追加(主要キー例: schedule.noSchedules, schedule.executionLog, schedule.enabled, schedule.disabled, schedule.lastExecuted, schedule.nextExecution)。src/i18n.tsPromise.all 内に schedule 名前空間の import 文を追加し、messages オブジェクトに schedule プロパティを追加する

テスト計画

既存テストへの影響

  • tests/unit/lib/db-migrations.test.ts: CURRENT_SCHEMA_VERSION の値がv17に変わるため、バージョン番号を検証するテストの更新が必要
  • PRAGMA foreign_keys = ON 追加による既存テストへの影響確認: foreign_keys を明示的に OFF にしていたテスト、またはCASCADE削除の未動作を前提としたテストがないか確認する
  • PRAGMA foreign_keys = ON による既存CASCADE定義の影響分析: 以下の既存テーブルのCASCADE/SET NULL定義がPRAGMA有効化後に動作を変える。それぞれの動作を確認するテストを追加すること:
    • chat_messages(worktree_id): ON DELETE CASCADE - worktree削除時にチャットメッセージが正しくCASCADE削除されること
    • session_states(worktree_id): ON DELETE CASCADE - worktree削除時にセッション状態が正しくCASCADE削除されること
    • worktree_memos(worktree_id): ON DELETE CASCADE - worktree削除時にメモが正しくCASCADE削除されること
    • clone_jobs(repository_id): ON DELETE SET NULL - リポジトリ削除時にclone_jobsのrepository_idがNULLに設定されること
    • 手動DELETE処理との競合確認: deleteWorktreesByIds 等の既存手動DELETE処理がCASCADE削除と競合しないこと(手動DELETE後にCASCADE削除対象レコードが既に存在しない場合でもエラーにならないこと)を検証する

新規ユニットテスト

  • env-sanitizer.test.ts: 全SENSITIVE_ENV_KEYS変数の除去テスト、非対象変数の保持テスト(S1-001/S4-001対応)
  • cmate-parser.test.ts: 正常パース、異常パース(不正Markdown)、空ファイル、未知セクション無視、個別エントリスキップ、パストラバーサル防御、cron式バリデーション(MIN_CRON_INTERVAL/MAX_CRON_EXPRESSION_LENGTH)、Unicode制御文字サニタイズテスト(S4-002対応)、NAME_PATTERNバリデーションテスト(S4-011対応)
  • schedule-manager.test.ts: タイマー起動/停止、同時実行防止、MAX_CONCURRENT_SCHEDULES制限、サーバー再起動リカバリ(running -> failed更新)、stopAllSchedules()のエクスポートと動作確認
    • 単一タイマー巡回方式のテスト:
      • 複数worktreeの巡回検知テスト(2つ以上のworktreeでCMATE.md変更が正しく検出されること)
      • エラー分離テスト(1つのworktreeのCMATE.mdパースエラーが他worktreeの処理に影響しないこと)
      • 動的worktree追加/削除テスト(サーバー実行中にworktreeが追加/削除された場合の巡回対象更新)
  • claude-executor.test.ts: 正常実行、タイムアウト(EXECUTION_TIMEOUT_MS)、メッセージ長制限(MAX_MESSAGE_LENGTH)、不正入力拒否、stripAnsi適用、出力サイズ制限(MAX_OUTPUT_SIZE超過時のtruncate動作)、cwdオプションの設定検証、SENSITIVE_ENV_KEYS除去テスト(S4-001対応)、MAX_STORED_OUTPUT_SIZE切り詰めテスト(S1-014対応)
  • DB CASCADE 削除テスト: worktree削除時に scheduled_executions と execution_logs が正しくCASCADE削除されることを検証(二重CASCADE削除パスの動作確認を含む)。テスト前提として PRAGMA foreign_keys = ON が有効であること

新規結合テスト

  • API CRUD 操作テスト: スケジュールの作成・取得・更新・削除・ログ取得

受入条件

基本機能

  • CMATE.md の ## Schedules セクションでスケジュールを定義できる
  • CMATE.mdは各worktreeディレクトリのルートに配置され、worktree単位で管理される
  • 定義したスケジュールが指定時刻に claude -p で自動実行される
  • 実行結果ログがDBに保存され、UIから確認できる
  • execution_logsにstatusカラムがあり、実行状態(running/completed/failed/timeout/cancelled)を管理できる
  • 既存Memoタブがリネームされ、メモと実行ログの両方が表示される(tab ID 'memo' は維持)
  • スケジュールの有効/無効を切り替えられる
  • サーバー再起動後もスケジュールが復元される(DB永続化)
  • サーバー起動時に execution_logsstatus='running' レコードが status='failed' に更新される(再起動リカバリ)
  • 既存のAuto-Yes機能と競合しない
  • 新規APIルートがmiddleware.tsの認証対象に含まれる

設計レビュー対応(S1: 設計原則)

  • env-sanitizer.ts が新規作成され、SENSITIVE_ENV_KEYS 配列(CLAUDECODE, CM_AUTH_TOKEN_HASH, CM_AUTH_EXPIRE, CM_HTTPS_KEY, CM_HTTPS_CERT, CM_ALLOWED_IPS, CM_TRUST_PROXY, CM_DB_PATH)による機密環境変数除去ロジックが一元管理されている(S1-001/S4-001)
  • schedule-manager.tsが500行超の場合、cmate-sync.tsに分離されている(S1-002)
  • parseCmateFile()Map<string, string[][]> を返す汎用設計になっている(S1-010)
  • PRAGMA foreign_keys = ON が runMigrations()に設定されている(S1-011/S2-010)
  • isValidWorktreeId() がauto-yes-manager.tsからimportされ再利用されている(S1-012)
  • NotesAndLogsPaneのpropsが { worktreeId: string; className?: string; } である(S1-013)
  • MAX_STORED_OUTPUT_SIZE(100KB)定数が存在し、一覧APIからresultカラムが除外されている(S1-014)

設計レビュー対応(S2: 整合性)

  • execution-logsが一覧API(resultカラム除外)と個別取得API(result含む)の2エンドポイントに分割されている(S2-002)

設計レビュー対応(S3: 影響分析)

  • stopAllSchedules() が同期的 process.kill(pid, 'SIGKILL') fire-and-forget方式で実装され、gracefulShutdownの3秒タイムアウト内で完了する(S3-001)
  • v17マイグレーションに既存孤児レコードのクリーンアップSQLが含まれている(S3-002)
  • initScheduleManager()await initializeWorktrees() 完了後に呼び出されている(S3-010)
  • db-migrations.test.tsCURRENT_SCHEMA_VERSIONgetMigrationHistory 件数が17に更新されている(S3-013)

設計レビュー対応(S4: セキュリティ)

  • Message列に対してUnicode制御文字(C0/C1制御文字、ゼロ幅文字、方向制御文字)のストリッピングが適用されている(S4-002)
  • Name列に対して NAME_PATTERN 正規表現によるバリデーションが適用され、不正な場合はエントリがスキップされる(S4-011)
  • 新規APIルートに isValidWorktreeId() + getWorktreeById() の2段階worktree IDバリデーションが適用されている(S4-010)
  • scheduleId / logId パラメータにUUIDv4フォーマットバリデーションが適用されている(S4-014)
  • セキュリティイベント(パストラバーサル検出、cron式バリデーション失敗等)に対して構造化ログが出力される(S4-012/S4-013)

セキュリティ

  • claude-executor.tsでexecFileを使用しシェルインジェクションを防止している
  • 実行タイムアウト(EXECUTION_TIMEOUT_MS)が設定され、無限ハングが防止されている
  • MAX_CONCURRENT_SCHEDULES による全体スケジュール数の上限制限がある
  • cron式のバリデーション(MIN_CRON_INTERVAL, MAX_CRON_EXPRESSION_LENGTH)が適用されている
  • CMATE.mdパース時のパストラバーサル防御(realpath + worktreeディレクトリ検証)がある

テスト

  • CASCADE削除テスト(二重パス含む)がパスする
  • 既存テストがすべてパスする

影響範囲

新規ファイル

ファイル 説明
src/lib/env-sanitizer.ts CLAUDECODE環境変数除去ユーティリティ(S1-001対応)
src/lib/cmate-parser.ts CMATE.md 汎用パーサー(Map<string, string[][]> 返却、S1-010対応)
src/types/cmate.ts CMATE.md 型定義(CmateConfig, ScheduleEntry 等)
src/lib/schedule-manager.ts スケジュール管理ロジック
src/lib/cmate-sync.ts CMATE.md変更検知・DB同期(schedule-manager.tsが500行超の場合に分離、S1-002対応)
src/lib/claude-executor.ts claude -p プロセス実行エンジン
src/app/api/worktrees/[id]/schedules/route.ts スケジュールCRUD API(GET/POST)
src/app/api/worktrees/[id]/schedules/[scheduleId]/route.ts スケジュール個別API(GET/PUT/DELETE)
src/app/api/worktrees/[id]/execution-logs/route.ts 実行ログ一覧API(resultカラム除外、S1-014対応)
src/app/api/worktrees/[id]/execution-logs/[logId]/route.ts 実行ログ個別API(result含む、S2-002対応)
src/components/worktree/ExecutionLogPane.tsx 実行ログ表示UI
src/components/worktree/NotesAndLogsPane.tsx メモ+実行ログのコンテナコンポーネント(S1-013対応)
locales/ja/schedule.json i18n: スケジュール関連の日本語メッセージ
locales/en/schedule.json i18n: スケジュール関連の英語メッセージ

変更ファイル

ファイル 変更内容
src/lib/db-instance.ts getDbInstance()dbInstance.pragma('foreign_keys = ON')runMigrations()に追加(S1-011/S2-010対応)
src/lib/db-migrations.ts v17マイグレーション追加(テーブル + 孤児レコードクリーンアップSQL)、validateSchema()のrequiredTables更新(S3-002対応)
src/lib/claude-session.ts env-sanitizer.ts の使用を検討(S2-011: 現在のtmux経由unsetとは別。createSession()のenv引数として使用可能な箇所がある場合のみ適用。なければ変更対象外)
src/lib/session-cleanup.ts cleanupWorktreeSessions() にスケジューラー停止を追加(S3-012対応)
src/components/worktree/WorktreeDetailRefactored.tsx タブラベル変更・NotesAndLogsPaneへの切替
src/components/worktree/LeftPaneTabSwitcher.tsx タブラベル変更(tab ID 'memo' は維持)
src/components/mobile/MobileTabBar.tsx タブラベル変更(tab ID 'memo' は維持)
src/i18n.ts schedule名前空間のimport追加(Promise.all内にimport文追加、messagesオブジェクトにscheduleプロパティ追加)
server.ts サーバー起動完了後に initScheduleManager()initializeWorktrees()に呼び出し追加。gracefulShutdown()stopAllSchedules() 追加(S3-001/S3-010対応)

変更不要ファイル(確認済み)

ファイル 理由
src/middleware.ts 変更不要。新規APIルートは既存の matcher パターンにより自動的に認証対象となる
src/config/auth-config.ts 変更不要。AUTH_EXCLUDED_PATHS への追加は不要

関連コンポーネント

  • src/lib/auto-yes-manager.ts - 設計パターンの参考(globalThis + setTimeout)。claude -p は独立プロセスのため、auto-yesポーリングには影響しない
  • src/lib/response-poller.ts - 応答ポーリング
  • src/config/auto-yes-config.ts - 時間設定の参考
  • src/lib/git-utils.ts - execFile使用のセキュリティパターン参照
  • src/lib/file-operations.ts - SEC-005〜009パターン参照(CMATE.mdパストラバーサル防御)
  • src/components/worktree/MemoPane.tsx - NotesAndLogsPaneから参照(変更は最小限またはなし)

将来の拡張(CMATE.md)

CMATE.mdは汎用設定ファイルとして以下のセクション追加を想定(別Issue):

セクション 用途 関連Issue
## Schedules スケジュール実行(本Issue) #294
## Queues コマンドキュー定義 #292
## Settings Auto-Yes等の設定 未定

レビュー履歴

イテレーション 1 (2026-02-23) - Stage 1: 通常レビュー

Must Fix:

  • S1-001: claude -p フラグの動作検証タスクを事前検証ステップとして追加。代替案(tmuxセッション経由)も記載
  • S1-002: cronライブラリとして croner を選定し設計方針・実装タスクに明記
  • S1-003: CMATE.mdファイル変更検知方式をポーリング方式(60秒間隔でstat().mtime比較)に決定

Should Fix:

  • S1-004: MemoPane拡張を方針B(新規コンテナコンポーネント NotesAndLogsPane.tsx でラップ)に決定
  • S1-005: tab ID 'memo' は維持し表示ラベルのみ変更する方針を明記
  • S1-006: scheduled_executions.id生成はrandomUUID()、worktree_id+nameにUNIQUE制約、upsert戦略を明記
  • S1-007: プロセスクリーンアップ(タイムアウト制御、サーバー終了時cleanup、同時実行防止)を追記
  • S1-008: セキュリティ要件(execFile/spawn使用、メッセージ長制限、stripAnsi適用)を追記
  • S1-009: CMATE.md配置を「各worktreeディレクトリのルート」に明確化
  • S1-010: i18n名前空間 schedule.json を追加する方針を明記
  • S1-011: execution_logsテーブルにstatusカラムを追加(running/completed/failed/timeout/cancelled)

Nice to Have:

  • S1-012: CMATE.mdパーサーのエラーハンドリング方針を実装タスクに追記
  • S1-013: globalThis変数の命名パターンは実装時に既存パターンに準拠(詳細は実装段階で決定)
  • S1-014: 新規APIルートの認証確認を受入条件に追加

イテレーション 2 (2026-02-23) - Stage 3: 影響範囲レビュー

Must Fix:

  • S3-001: globalThis変数名を明示(__scheduleManagerStates, __scheduleActiveProcesses)。declare globalブロックの型定義も注記
  • S3-002: CASCADE削除の二重パス(schedule_id + worktree_id)の動作検証テストケースを追加
  • S3-003: validateSchema()requiredTables 配列に 'scheduled_executions''execution_logs' を追加する実装タスクを明記
  • S3-004: claude -p と既存tmuxセッションの並行動作時の副作用分析を設計方針に追記(auto-yesポーリング非影響、ファイル変更の注意事項、.claude/ディレクトリ同時アクセス)

Should Fix:

  • S3-005: MAX_CONCURRENT_SCHEDULES 定数(例: 100)をschedule-manager.tsに追加。単一タイマー巡回方式を推奨
  • S3-006: サーバー起動時の execution_logs status='running' レコードのリカバリロジック(-> 'failed', reason: server_restart)を追加
  • S3-007: session-cleanup.ts を変更ファイルに追加(cleanupWorktreeSessions()にスケジューラー停止を組み込み)
  • S3-008: cron式バリデーション(MIN_CRON_INTERVAL, MAX_CRON_EXPRESSION_LENGTH)を追加
  • S3-009: CMATE.mdパース時のパストラバーサル防御(realpath() + worktreeディレクトリ検証、file-operations.tsパターン準拠)を追加
  • S3-010: テスト計画を詳細化(既存テスト影響分析、新規テスト粒度の明記、テスト計画セクション新設)
  • S3-011: NotesAndLogsPaneのサブタブ状態管理設計を明記(内部useState、WorktreeDetailRefactored.tsxへの非漏洩、初期値'memo')
  • S3-012: middleware.ts / auth-config.ts が変更不要であることを「変更不要ファイル」セクションとして明記

Nice to Have:

  • S3-013: execution_logs にインデックス追加(worktree_id+created_at DESC, schedule_id+created_at DESC)。scheduled_executions にも (enabled, next_execute_at) インデックスを追加。将来検討として MAX_EXECUTION_LOGS_PER_SCHEDULE を記載
  • S3-014: CMATE.mdとCLAUDE.mdの区別に関する注記を追加
  • S3-015: scheduled_executions の (enabled, next_execute_at) 複合インデックスを追加(S3-013と統合して対応)

イテレーション 3 (2026-02-23) - Stage 5: 通常レビュー(2回目)

Must Fix:

  • S5-001: db-instance.tsgetDbInstance()dbInstance.pragma('foreign_keys = ON') を追加する実装タスクを明記。SQLiteデフォルトの foreign_keys = OFF ではCASCADE削除が機能しないため、本Issueの前提条件として必須。変更ファイル一覧にも db-instance.ts を追加

Should Fix:

  • S5-002: src/i18n.ts を変更ファイル一覧に追加(schedule名前空間のimport追加が必要)
  • S5-003: ID生成方式の記述を randomUUID()import { randomUUID } from 'crypto')に修正(既存 db.ts パターンと正確に一致)
  • S5-004: scheduled_at / repeat_interval_ms カラムをスキーマから除外(YAGNI原則。本Issueではcron_expressionのみ使用、将来必要時にマイグレーションで追加)
  • S5-005: スケジュール管理UIの配置先と機能範囲を明記(ExecutionLogPane内のスケジュール一覧セクション、UIからの作成・編集はスコープ外)

Nice to Have:

  • S5-006: worktreeディレクトリパス取得方法を明記(DBのworktreesテーブルからpath取得、path.join()でCMATE.mdパス構築)
  • S5-007: cli_tool_id カラムの将来拡張意図を注記(本Issueではデフォルト 'claude' のみ使用)
  • S5-008: スケジューラー初期化の呼び出し元を明記(server.tsのサーバー起動完了後に initScheduleManager() を呼び出し)。変更ファイル一覧に server.ts を追加

イテレーション 4 (2026-02-23) - Stage 7: 影響範囲レビュー(2回目)

Should Fix:

  • S7-001: server.tsの gracefulShutdown()stopAllSchedules() 停止処理の追加を変更ファイル一覧とschedule-manager.tsの実装タスクに明記
  • S7-002: PRAGMA foreign_keys = ON有効化による既存CASCADE定義(chat_messages, session_states, worktree_memos, clone_jobs)への影響分析をテスト計画に追記。手動DELETE処理との競合確認テストも追加
  • S7-003: session-cleanup.tsの WorktreeCleanupResult interface拡張方針(オプショナルフィールド追加 or 既存フィールド活用)を明記
  • S7-004: schedule-manager.test.tsに単一タイマー巡回方式特有のテストケース(複数worktree巡回検知、エラー分離、動的worktree追加/削除)を追記

Nice to Have:

  • S7-005: claude-executor.tsに MAX_OUTPUT_SIZE 定数(例: 1MB)による出力サイズ制限を追加。execFileのmaxBufferオプション設定やspawnでのバイト数累算を明記
  • S7-006: CMATE.mdのGit管理方針を設計方針に追記(.gitignore追加推奨、チーム共有時はGit管理対象も可)
  • S7-007: claude-executor.tsの child_process cwdオプションにworktreeのpathを設定する方針を明記(DBの信頼済みソースを使用)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions