Skip to content

fix: 選択肢プロンプトの送信がClaude Codeに認識されない(promptCheck再検証失敗時のフォールバック不備) #287

@Kewton

Description

@Kewton

Note: このIssueは 2026-02-15 にレビュー結果を反映して更新されました。
詳細: dev-reports/issue/287/issue-review/

概要

モバイル画面の「Claudeからの確認」ダイアログで選択肢(例: 1. Yes)を選んで「送信」しても、Claude Codeが入力を認識せずプロンプトが残り続ける。

再現手順

  1. Claude Codeセッションで複数選択肢プロンプト(AskUserQuestion)が表示される
  2. CommandMateのモバイルUIで選択肢を選択
  3. 「送信」ボタンをタップ
  4. → Claude Codeが応答せず、プロンプトが残り続ける

promptCheck再検証が失敗する条件

上記手順のステップ3で route.ts:L72-89captureSessionOutput() が例外を発生させる場合に本バグが発現する。具体的な失敗条件は以下の通り:

  • tmuxセッション出力のキャプチャが5000msタイムアウトする場合
  • tmuxセッション名が不正または存在しない場合
  • セッションが既に終了している場合

補足: 本バグは上記条件下でのみ発生し、captureSessionOutput() が正常に完了する場合は既存のカーソルキー方式が正しく適用される。

原因

src/app/api/worktrees/[id]/prompt-response/route.ts の72〜89行目にあるプロンプト再検証処理がサイレント失敗した場合、promptChecknull のままとなり、96〜98行目の isClaudeMultiChoice 判定が常に false になる。

結果、149〜157行目のテキスト入力フォールバック(sendKeys("1") + Enter)が実行されるが、Claude Codeの選択肢プロンプトはカーソルキーベースのナビゲーション(Arrow Up/Down + Enter)を要求するため、文字列入力は無視される。

処理フロー

UI: selectedOption.toString() → "1" → onRespond("1")
    ↓
API (prompt-response/route.ts):
  [L72-89] captureSessionOutput() → 例外発生 → promptCheck = null
  [L96-98] promptCheck?.promptData?.type → undefined → isClaudeMultiChoice = false
  [L149-157] sendKeys("1") + Enter → Claude Codeは認識しない ✗

期待される動作 vs 実際の動作

期待される入力 実際に送られる入力
sendSpecialKeys(["Enter"]) (デフォルト選択時) sendKeys("1") + sendKeys("", Enter)
カーソルキー操作 → Enter 文字列 "1" → Enter

影響範囲

  • Claude Codeの複数選択肢プロンプト(AskUserQuestion)のみ
  • Yes/Noプロンプトはテキスト送信で動作するため影響なし
  • Codexには影響なし
  • src/lib/auto-yes-manager.ts には影響なし(L318-321で promptDetection.isPromptfalse の場合は早期リターンする設計であり、promptDetectionnull になるケースは発生しない。route.tsとは異なる構造のため本バグの影響範囲外)
    • 注記: route.tsauto-yes-manager.ts(L343-399)のカーソルキー送信ロジックには重複が存在する。本Issueでは影響範囲外のため対応しないが、重複解消のリファクタリングは別Issueでの検討を推奨する

影響を受けるAPIクライアント

prompt-response APIを呼び出すクライアントは以下の2箇所であり、いずれもアプローチBの修正対象となる:

  1. WorktreeDetailRefactored.tsxhandlePromptRespond: ユーザー手動応答パス(PromptMessage.tsx, PromptPanel.tsx, MobilePromptSheet.tsxonRespond コールバック経由)
  2. useAutoYes.ts の fetch呼び出し(L85-89): Auto-Yes自動応答パス。現在 { answer, cliTool } のみをリクエストボディに含めており、promptType/defaultOptionNumber が含まれていない。promptData は関数パラメータとして受け取っている(L30: promptData: PromptData | null)ため修正は容易

修正方針案

アプローチ比較

promptChecknull(再検証失敗)の場合にカーソルキー方式で送信するための2つのアプローチ:

アプローチA: API側のみでフォールバック

cliToolId === 'claude' かつ answer が数値文字列の場合にカーソルキー方式を仮定する。

  • メリット: UI側の変更不要。修正範囲が小さい
  • デメリット: promptTypeが不明なため、Yes/Noプロンプトの再検証失敗時にも誤ってカーソルキー方式が適用されるリスクがある
  • リスク: answer="1" がYes/Noの"Yes"なのか、選択肢の1番なのかを区別できない

アプローチB: UI側からpromptData情報を送信(推奨)

UI側(handlePromptRespond)からリクエストボディに promptData 情報(type, defaultOptionNumber)を含め、API側でその情報を利用してカーソルキー方式を決定する。

  • メリット: 正確なpromptType判定が可能。誤送信リスクが低い
  • デメリット: PromptResponseRequest 型の拡張とUI側の修正が必要
  • 修正箇所: 下記「アプローチBの修正箇所」セクション参照

アプローチBの修正箇所

推奨する実装戦略: 非破壊的アプローチ

onRespond のシグネチャ (answer: string) => Promise<void> は変更せず、handlePromptRespond の内部で state.prompt.data から直接 promptType/defaultOptionNumber を取得してリクエストボディに含める。この戦略により、MobilePromptSheet.tsx, PromptPanel.tsx, PromptMessage.tsxonRespond インターフェースは不変となり、UI側の修正は handlePromptRespond の内部修正のみで済む。

根拠: WorktreeDetailRefactored.tsxhandlePromptResponduseCallback で定義されており、関数内部で state.prompt.data にアクセスすることが可能(dependency配列への追加が必要)。シグネチャを変更する破壊的アプローチでは PromptMessage.tsx (L18), MobilePromptSheet.tsx (L44), PromptPanel.tsx (L46) の全ての onRespond 型定義も同期が必要となり、修正範囲が不必要に拡大する。

実装時の注意点: useCallback のdependency配列(現在: [worktreeId, actions, fetchCurrentOutput, activeCliTab])に state.prompt.data を追加すると、promptData が変化するたびに関数が再生成される。promptData はポーリング(2秒/5秒間隔)の度に更新される可能性があるため、理論的には関数の再生成頻度が上がる。ただし、React.memo で保護されている PromptPanel/MobilePromptSheet は他のpropsも同時に変わるため、実質的なパフォーマンス影響は無視できるレベルである。

defaultOptionNumber の導出ロジック

リクエストボディに含める defaultOptionNumber は、PromptData の型に応じて以下のように導出する:

  • multiple_choice 型の場合: MultipleChoicePromptData.options 配列から options.find(o => o.isDefault)?.number で導出する。MultipleChoiceOption.isDefaultboolean 型であり、デフォルト選択肢の numbernumber 型)を取得する必要がある
  • yes_no 型の場合: YesNoPromptData.defaultOption'yes' | 'no' 型(文字列)であり、カーソルキー方式は不要なため defaultOptionNumber は送信しない(undefined

注意: Issueの以前の記載では defaultOption?: number としていたが、これは multiple_choice 型専用のフィールドであり、YesNoPromptData.defaultOption'yes' | 'no' 型)とは異なる。混同を避けるため、リクエストボディのフィールド名を defaultOptionNumber とする。

フォールバック判定ロジックの疑似コード

route.ts 内での promptCheckbody.promptType の優先順位:

// promptCheck が存在する場合はそちらを優先し、null の場合のみリクエストボディにフォールバック
const isClaudeMultiChoice =
  cliToolId === 'claude' &&
  (
    // 通常パス: promptCheck が正常に取得できた場合
    promptCheck?.promptData?.type === 'multiple_choice' ||
    // フォールバックパス: promptCheck が null(再検証失敗)の場合
    (promptCheck === null && body.promptType === 'multiple_choice')
  ) &&
  /^\d+$/.test(answer);

// カーソルキーのoffset計算
if (isClaudeMultiChoice) {
  // 通常パス: promptCheck からデフォルト選択肢番号を取得
  // フォールバックパス: body.defaultOptionNumber を使用(undefined の場合はフォールバック値 1)
  const defaultNum = promptCheck
    ? (promptCheck.promptData?.options?.find(o => o.isDefault)?.number ?? 1)
    : (body.defaultOptionNumber ?? 1);  // ★ ?? 1 が必要(後方互換性のため)
  const offset = selectedNum - defaultNum;
  // ... カーソルキー送信
}

defaultOptionNumber ?? 1 フォールバックの根拠: route.ts の既存コード(L103-104)では defaultNum = defaultOption?.number ?? 1 としてフォールバック値 1 を使用している。フォールバックパスでも同様の ?? 1 が必要である。特に、UIが古いバージョン(promptType/defaultOptionNumber を送信しない)でAPIが新バージョンの場合、body.defaultOptionNumberundefined となるため、このフォールバック値が後方互換性を担保する。

修正ファイル一覧

ファイル 修正内容 変更タイプ
src/app/api/worktrees/[id]/prompt-response/route.ts PromptResponseRequestpromptType?: string, defaultOptionNumber?: number を追加(オプショナル。後方互換性維持)。promptCheck=null 時にリクエストボディの promptType を参照してカーソルキー方式を判定。フォールバックパスでは body.defaultOptionNumber ?? 1 でデフォルト値を使用。PromptResponseRequest 型はこのIssueではroute.ts内のローカル定義のまま拡張し、src/types/models.ts への共有化は別Issueとする modify
src/components/worktree/WorktreeDetailRefactored.tsx handlePromptRespond 内部で state.prompt.data から promptType を取得し、multiple_choice 型の場合は options.find(o => o.isDefault)?.numberdefaultOptionNumber を導出。リクエストボディの JSON.stringify() に含める。useCallback のdependency配列に state.prompt.data を追加 modify
src/hooks/useAutoYes.ts L85-89の fetch 呼び出しで { answer, cliTool }promptTypepromptData.type)と defaultOptionNumbermultiple_choice 型の場合のみ promptData.options.find(o => o.isDefault)?.number)を追加 modify
tests/unit/api/prompt-response-verification.test.ts promptCheck 失敗時のテストケース更新。promptType 付きリクエストのテスト追加。defaultOptionNumberundefined のケースのテスト追加(?? 1 フォールバック動作の検証) modify
tests/integration/worktree-detail-integration.test.tsx prompt-response API呼び出しのリクエストボディ内容(answer, cliTool, promptType, defaultOptionNumber)を検証するテストケースを追加(現状はAPI呼び出しの存在確認のみ) modify

UI側で変更不要なファイル(非破壊的アプローチの場合)

以下のファイルは onRespond シグネチャが不変のため、修正不要:

  • src/components/worktree/PromptMessage.tsxonRespond: (answer: string) => Promise<void> 不変)
  • src/components/mobile/MobilePromptSheet.tsx (同上)
  • src/components/worktree/PromptPanel.tsx (同上)

推奨方針

アプローチBを推奨(非破壊的実装戦略)。UIコンポーネント(MobilePromptSheet.tsx, PromptPanel.tsx)は既に promptData を保持しているため、handlePromptRespond 内部で state.prompt.data から情報を取得するアプローチが最も安全かつ正確であり、かつ修正範囲が最小限に抑えられる。

受け入れ条件

  • promptCheck 再検証が失敗した場合(promptCheck=null)でも、Claude Codeの複数選択肢プロンプト(AskUserQuestion)にカーソルキー方式で正しく応答できること
  • Yes/Noプロンプトの既存動作に影響がないこと
  • Codexの既存動作に影響がないこと
  • 既存テスト(tests/unit/api/prompt-response-verification.test.ts)が引き続きパスすること(必要に応じてテスト更新)
  • promptCheck 再検証が失敗するケースのテストが追加されていること
  • useAutoYes.ts の prompt-response API呼び出しに promptType/defaultOptionNumber が含まれること(Auto-Yesパスでの同一バグ再発防止)
  • 結合テスト(worktree-detail-integration.test.tsx)で prompt-response リクエストボディの内容(answer, cliTool, promptType, defaultOptionNumber)が検証されること
  • promptType/defaultOptionNumber フィールドはオプショナル(?)として設計され、フィールド未送信時の後方互換性が保たれること
  • defaultOptionNumberundefined の場合にフォールバック値 1 が使用されること(?? 1)。UIが古いバージョンでAPIが新バージョンの場合のテストを含むこと

関連ファイル

  • src/app/api/worktrees/[id]/prompt-response/route.ts (主要修正対象)
  • src/lib/cli-session.ts (captureSessionOutput の実装元。例外発生パターンの理解に必要)
  • src/components/worktree/WorktreeDetailRefactored.tsx (handlePromptRespond修正対象)
  • src/hooks/useAutoYes.ts (prompt-response API呼び出し修正対象)
  • src/components/mobile/MobilePromptSheet.tsx (非破壊的アプローチでは変更不要)
  • src/components/worktree/PromptPanel.tsx (同上)
  • src/components/worktree/PromptMessage.tsx (同上。onRespondコールバック経由でAPIを呼び出す)
  • src/lib/tmux.ts (sendKeys / sendSpecialKeys)
  • src/types/models.ts (PromptResponseRequest, PromptData, MultipleChoiceOption 型定義)
  • tests/unit/api/prompt-response-verification.test.ts (テスト更新対象)
  • tests/integration/worktree-detail-integration.test.tsx (リクエストボディ検証追加対象)

関連Issue

スクリーンショット

問題発生時のモバイル画面:
Screenshot


レビュー履歴

イテレーション 1 - Stage 3: 影響範囲分析 (2026-02-15)

  • MF-1: useAutoYes.ts の prompt-response API呼び出し(L85-89)を影響範囲・修正箇所リストに追加。Auto-Yesパスでの同一バグ再発防止
  • SF-1: PromptMessage.tsxonRespond コールバックパスを影響範囲に明記。非破壊的アプローチでは変更不要であることを明示
  • SF-2: onRespond シグネチャの実装戦略(非破壊的アプローチ推奨)を明記。handlePromptRespond 内部で state.prompt.data から取得する戦略の根拠を記載
  • SF-3: 受け入れ条件に結合テストのリクエストボディ検証を追加。修正ファイル一覧に worktree-detail-integration.test.tsx を追加

イテレーション 2 - Stage 5: 整合性レビュー (2026-02-15)

  • SF-1: defaultOption フィールド名を defaultOptionNumber に変更し、multiple_choice 型専用であることを明確化。MultipleChoiceOption.isDefault(boolean)から options.find(o => o.isDefault)?.number で導出するロジックを「defaultOptionNumberの導出ロジック」セクションとして追加。YesNoPromptData.defaultOption'yes' | 'no' 型)との混同を防止
  • SF-2: useAutoYes.ts の影響範囲説明で「propsとして受け取っている(L76)」を「関数パラメータとして受け取っている(L30)」に修正。L76は promptKey 生成行であり、promptData受け取り箇所ではない
  • NTH-1: route.ts のフォールバック判定ロジックの疑似コードを「フォールバック判定ロジックの疑似コード」セクションとして追加。promptCheck が存在する場合はそちらを優先し、null の場合のみリクエストボディの promptType にフォールバックする判定順序を明示
  • NTH-2: PromptResponseRequest 型の配置方針を明記。本Issueでは route.ts 内のローカル定義のまま拡張し、src/types/models.ts への共有化は別Issueとするスコープ判断を修正ファイル一覧に追記

イテレーション 2 - Stage 7: 影響範囲分析 (2026-02-15)

  • SF-1: フォールバック判定ロジックの疑似コードに defaultOptionNumber ?? 1 フォールバックを明記。body.defaultOptionNumberundefined の場合(UIが古いバージョン)にデフォルト値 1 を使用する根拠と、既存コード(L103-104)との一貫性を記載。受け入れ条件に defaultOptionNumberundefined の場合のテストを追加。テスト修正ファイル一覧にも defaultOptionNumber=undefined ケースの追加を明記
  • NTH-1: auto-yes-manager.tsroute.ts のカーソルキーロジック重複(L343-399 vs L96-148)のリファクタリングについて、影響範囲セクションに別Issueでの検討を推奨する注記を追加
  • NTH-2: handlePromptResponduseCallback dependency配列に state.prompt.data を追加した場合のパフォーマンス影響に関する注意点を「推奨する実装戦略」セクションに追加。ポーリング頻度による関数再生成と React.memo による保護の関係を記載

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions