-
Notifications
You must be signed in to change notification settings - Fork 2
Description
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.ts、cmate-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時に.gitignoreにCMATE.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(命令行列)との関係: 完全に独立
- 命令行列(実行順序を指定しての実行) #292(命令行列)は「即時の順次実行」
- スケジュール実行の登録 #294(本Issue)は「時刻指定の自動実行」
- 別機能として独立して実装する
- 将来的にCMATE.mdの
## Queuesセクションとして 命令行列(実行順序を指定しての実行) #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ルート経由でオンデマンド開始されるのとは異なり、スケジューラーはサーバー起動時に自動開始する必要があるため、カスタムサーバーからの明示的な初期化が必要。
主要な変更点
- 環境変数サニタイズユーティリティ:
env-sanitizer.tsを新規作成。SENSITIVE_ENV_KEYS配列(CLAUDECODE,CM_AUTH_TOKEN_HASH,CM_HTTPS_KEY等)による機密環境変数の一元除去(設計レビューS1-001/S4-001対応) - CMATE.mdパーサー(汎用): Markdown見出しセクション単位でテーブルをパースする汎用パーサー。
parseCmateFile()はMap<string, string[][]>を返す。## Schedulesセクションを本Issueで実装し、他セクションは将来のIssueで対応(設計レビューS1-010対応: 汎用設計範囲の限定) - DBマイグレーション(v17):
scheduled_executions+execution_logsテーブルを追加。既存孤児レコードのクリーンアップSQLを含む(設計レビューS3-002対応) - サーバーサイドスケジューラー:
schedule-manager.tsを新規作成、globalThisでタイマー状態を管理。500行超の場合はcmate-sync.tsに分離(設計レビューS1-002対応) claude -p実行エンジン: 非対話モードでの新規プロセス起動・結果取得(セキュリティ対策・タイムアウト制御・SENSITIVE_ENV_KEYS除去込み)。MAX_OUTPUT_SIZE(1MB)とMAX_STORED_OUTPUT_SIZE(100KB)の2段階サイズ制限(設計レビューS1-014対応)- PRAGMA foreign_keys = ON の有効化:
db-instance.tsのgetDbInstance()にdbInstance.pragma('foreign_keys = ON')をrunMigrations()の前に追加し、CASCADE削除を正しく機能させる(設計レビューS2-010対応) - APIルート: スケジュールのCRUD操作 + 実行ログ取得用エンドポイント(一覧API: resultカラム除外、個別取得API: result含む。設計レビューS1-014/S2-002対応)
- UIコンポーネント: 新規コンテナコンポーネントによるMemoタブ拡張(メモ+実行ログ表示)。
NotesAndLogsPaneprops:{ worktreeId: string; className?: string; }(設計レビューS1-013対応) - 入力サニタイズ: 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.ts の getDbInstance() に dbInstance.pragma('foreign_keys = ON') を追加する。これにより、本Issue の CASCADE 削除だけでなく、既存テーブル(chat_messages, session_states, worktree_memos 等)の CASCADE 削除も正しく機能するようになる。既存テストへの影響を確認すること。
CASCADE削除に関する注意: execution_logs は schedule_id と worktree_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戦略:
worktree_id + nameで既存レコードを検索(UNIQUE制約で一意性を保証)- 存在すれば UPDATE(cron_expression, message 等を更新)
- なければ INSERT(新規レコード作成)
- CMATE.md から消えたレコードは
enabledをfalseに設定(削除ではなくソフト無効化)
将来検討: 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-format で text(デフォルト)/ json / stream-json を選択可能 |
| JSON出力 | result, session_id 等のメタデータを含む構造化出力。--json-schema で出力スキーマ指定も可能 |
| ツール自動承認 | --allowedTools で Bash,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.tsのsanitizeSessionEnvironment()と同じパターンであり、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.tsのgetDbInstance()に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_logsのstatus='running'レコードをstatus='failed'(reason:server_restart)に更新するリカバリロジックを実装 - 初期化呼び出し元:
server.ts(カスタムサーバー)のサーバー起動完了後にinitScheduleManager()を呼び出す。auto-yes-manager.ts(APIルート経由オンデマンド開始)とは異なり、サーバー起動時の自動開始が必要 - 停止関数のエクスポート:
stopAllSchedules()エクスポート関数を作成する。タイマーのキャンセルと実行中子プロセスの同期的process.kill(pid, 'SIGKILL')fire-and-forget方式によるkillを行い、server.tsのgracefulShutdown()の3秒タイムアウト内で確実に完了させる(設計レビューS3-001対応) - 責務分割: 500行超の場合は
src/lib/cmate-sync.tsにCMATE.md変更検知・DB同期ロジックを分離する(設計レビューS1-002対応) - 初期化順序:
initScheduleManager()はawait initializeWorktrees()の完了後に呼び出すこと(設計レビューS3-010対応)
- globalThis変数名:
-
src/lib/claude-executor.ts新規作成(claude -pプロセス起動・結果取得)- タイムアウト制御:
EXECUTION_TIMEOUT_MS定数(例: 5分)と AbortController/kill信号による強制終了 - cwdオプション:
child_processのcwdオプションに worktree のpathを設定する。cwdパスはDBのworktreesテーブルから取得した信頼済みソースを使用すること(process.cwd() を使用しないこと) - 出力サイズ制限:
MAX_OUTPUT_SIZE定数(例: 1MB)による出力サイズ制限。execFileを使用する場合はmaxBufferオプションを明示的に設定する。spawnを使用する場合はデータイベントでバイト数を累算し制限値で読み取りを停止する。制限を超えた場合は末尾をtruncateし「...(truncated)」を付与する - 環境変数サニタイズ:
env-sanitizer.tsのsanitizeEnvForChildProcess()を使用し、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'(既存動作互換)
- tab ID
-
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.tsのPromise.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_logsのstatus='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.tsのCURRENT_SCHEMA_VERSIONとgetMigrationHistory件数が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_logsstatus='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.tsのgetDbInstance()に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の
WorktreeCleanupResultinterface拡張方針(オプショナルフィールド追加 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_processcwdオプションにworktreeのpathを設定する方針を明記(DBの信頼済みソースを使用)