Skip to content

v0.42.0

Choose a tag to compare

@Kewton Kewton released this 27 Sep 14:07
· 18 commits to main since this release
86e4c47

[0.42.0] - 2026-09-27

Highlight: 同じマシンで複数の CommandMate サーバを動かすと、同名の tmux セッションを奪い合い、別サーバのエージェントの画面が見えて送信・停止までできていた問題を直した(#2865・#2866・#2867。セッション名にサーバごとの名前空間を入れ、旧名のセッションは作業ディレクトリが一致するものだけ旧名のまま引き継ぐ)。codex 0.157 への追従として、/model と trust の画面の判定(#2868・#2884)と、共有デーモン経由で hook の送信元インスタンスが取り違えられる問題(#2874・#2891、実機で codex-3 の返答が codex-3 に入ることを確認)を直した。送れない選択ウインドウでは Send を出さずに直接入力モードへ案内する(#2869・#2870・#2887・#2888)。

Added

  • feat(verify): env-clean が $HOME 直下の無関係なエントリで不合格にならないよう、verify.yaml の options.envCleanIgnoreHomeEntries で名前ごとに除外できるようにする (#2890): 2026-09-27 の /orchestrate run で、ワーカーのコマンドと結び付かない .semgrep(並行ワーカー 3 本すべてに出た)と .commandagent(別プロジェクトのツールが作った)が home-entries の unattributed な追加として 7 本中 5 本の検証を exit 20 にしていた。除外できるのは VOLATILE_ENTRY_PATTERNS の固定リストだけで、リポジトリごとに足す手段が無かった。options.envCleanIgnoreHomeEntries(文字列の配列、既定 []、最大 32 件)を足し、$HOME 直下のエントリ名との完全一致(glob・正規表現なし。.semgrep-x は .semgrep に一致しない)で home-entries probe の追加・削除の両方を数えないようにした(commandmate-entries などほかの probe には効かない)。除外は比較の段階(diffEnvSnapshots の 4 つ目の引数 { ignoreHomeEntries })だけで、採取済みのベースラインは書き換えないので、後からリストに足した名前は既存 task にも効く。除外した項目は黙って消さず、レポートに ignored (options.envCleanIgnoreHomeEntries): .semgrep の 1 行で出す。配列でない・文字列でない要素・空文字・/ や NUL を含む・. / ..・33 件以上は VerifyConfigError(実行は error で終わり、無視して通すことはしない)。このリポジトリの verify.yaml への追加は含まない(未対応のサーバが未知キーで設定エラーにするため、マージ・再ビルド後に足す)。仕様は docs/design/task-contract.md §2.6。

  • feat(worktree): Send が効かないプロンプトのウインドウから直接入力モードへ案内する (#2869): 同じウインドウ(type・question・選択肢ラベルの指紋が同じ)が Send の後も続けて 3 回表示されたとき(Send が 2 回続けて画面を変えなかったとき)、PC の PromptPanel とスマホの MobilePromptSheet に「送信がうまくいかない場合は、直接入力モードで操作してください。」と「直接入力に切り替える」リンクを出す。PC はそのペインの直接入力バーを開き、スマホはターミナルタブ・ターミナル表示へ切り替えてから直接入力キーボードを開く(セッションが動いていないときはリンクを出さない)。数えるのは新規 hook usePromptStuckCounter(PROMPT_STUCK_THRESHOLD=3、ウインドウが 10 秒消えたら/対象が変わったら 0 に戻す)で、送信の成否ではなく画面が変わったかどうかだけで判定する。

Changed

  • feat(cli): CLI がサーバの公開するセッション名で tmux セッションを扱う (#2867): #2866 でセッション名が mcbd-{ns}-{cli}-{worktreeId}[-{suffix}] になり、ns はサーバの DB にあって CLI では計算できないため、サーバが GET /api/worktrees のトップレベルに tmuxSessionNamespace、GET /api/worktrees/[id] の agentInstances[].sessionName に実際に使っている名前(旧形式の採用を反映)を返すようにした。commandmate attach は sessionName で has-session・attach し、commandmate instances の TMUX_SESSION 列(--json の tmuxSession)も sessionName を表示し(どちらも古いサーバでは旧形式を組み立てる)、ls --json の tmuxSession は tmuxSessionNamespace から新形式を組み立て(旧形式で採用中のセッションも新形式名で表示される一覧用の近似)、whoami は lib/cli-tools/session-name.ts の parseSessionName で新旧両方の形式を分解する。CM_LIVE_ATTACH_HOOK=on の live attach hook も新形式の claude セッションで発火する。古い CLI は新しいサーバのセッション名を組み立てられない。CLI とサーバを同じ版にすること。

  • feat(session): tmux セッション名にサーバごとの名前空間を入れ、同名 worktree を持つ 2 台目のサーバでも自分のセッションを起動できるようにする (#2866): サーバは初回起動時に 16 進 8 桁の名前空間を作って app_settings(tmux_session_namespace)に保存し(形式違反の保存値は作り直して warn)、以後に作るセッション名は mcbd-{ns}-{cli}-{worktreeId}[-{suffix}] になる(初期化に失敗したとき・CLI・テストでは従来の mcbd-{cli}-{worktreeId}[-{suffix}] のまま)。更新前から動いている旧形式のセッションは rename せず、起動時に #{session_path} が worktree と一致して新形式名が空いているものを採用してそのまま表示・送信・停止でき(旧版に戻したときや旧版のサーバが並行して動くときにも失われない)、停止されるか消えたのを検知した後の起動から新形式名で作る。起動時の worktree ID 追従は旧形式と自サーバの名前空間の名前だけを対象にし、別の名前空間の名前には触れない。env-clean ゲートはどの名前空間の名前も worktree に帰属させる。commandmate CLI(attach など)・スキル・スクリプトの新形式への追従は B2 で行う。

Fixed

  • fix(skills): orchestrate-monitor に --no-auto-approve と promptData 判定の PROMPT 承認/保留分岐を移植し、sync-map の cmate-verify fixture 誤検知を直す (#2904): skills-sync-map.mjs check --counterpart が commandmate-skills 側の構造差(skills/cmate-verify の fixture・run-tests.sh を package 外の tests/fixtures/cmate-verify/ に置く配置)を byte-identical 違反として 48 件出し続けていたため、file エントリに任意の counterpartPath を持たせられるようにして .claude/skills/sync-map.json の該当 48 件に設定し(run-tests.sh は前提パスが異なるため port-required に変更)、違反を 0 件にした。あわせて .claude/skills/orchestrate-monitor/scripts/monitor.sh・monitor-lib.sh に commandmate-skills PR #77(skills #59)の --no-auto-approve フラグと、promptData から二択かつ default が肯定のときだけ Enter を送り、それ以外は prompt_hold_reason で理由付きに保留する判定(held/heldsig 状態、ml_autoyes_suppression_reason・ml_prompt_default_label 等)を移植し、両リポジトリのコメントを除いたコード差分を 0 行にした(monitor-lib.sh の ML_CLI_TOOL_IDS 末尾の command-code は skills に無い CLI ツール対応のための既存差分で対象外)。

  • fix(worktree-cleanup): /worktree-cleanup Phase 3 のtmuxセッション終了パターンを現行の命名規則に合わせる (#2903): 旧パターン(feature-${ISSUE_NO}-worktree$)は現行のセッション名(mcbd-<cli>-commandmate-issue-<N>[-<suffix>]、#2866以降はmcbd-<ns>-<cli>-commandmate-issue-<N>[-<suffix>])に一致せず、git worktree remove後もワーカーのtmuxセッションが残っていた。CommandMate経由(roster全インスタンスのkill)を第一手段にし、取りこぼしはtmuxを完全一致(kill-session -t "=name")で回収する2段構成にした。

  • test(api): api-respond-cli-tool テストの terminal-broadcast モックに欠落していた export を追加 (#2902): broadcastTerminalSnapshot および broadcastSessionStatus をモック定義に追加し、モジュール export との不一致による将来の unhandled rejection を防止

  • fix(cmate-verify): standalone 検証ランナー(verify-run.sh)の awk パーサに options.envCleanIgnoreHomeEntries を追加する (#2901): #2890 で本体(TS ローダ)が受理するようになった $HOME 直下の env-clean 除外リストを、awk パーサの許可キー集合(baseRef・skipInPrimaryCheckout・maxLogTailBytes・requireCommit・requireEnvClean の 5 つ)は知らず未知キーとして exit 2 で拒否していたため、このリポジトリの verify.yaml に足せなかった。ブロック形式・フロー形式(空の [] も可)の両方を受理し、要素は本体と同じ規則(非空・/ を含まない・./.. でない・33 件以上はエラー)で検証する — standalone ランナーは env-clean を判定しないため受理した値は内部 OPT レコードとして流すだけで判定には使わない。.agents/skills/cmate-verify/scripts/verify-run.sh にも同じ変更をミラーした。.commandmate/verify.yaml へのキー追加は含まない(稼働中のサーバが #2890 以前のコードで読むため、マージ・再ビルド後にオーケストレーターが行う)。commandmate-skills 側の同名ファイル(Kewton/commandmate-skills#270)と advisor の port は別 Issue のスコープで、その移植と .claude/skills/sync-map.json の pin 更新までは sync-map.test.ts が想定どおり赤。

  • fix(codex): 既に動いている codex の共有デーモンにつながって hook のインスタンスが取り違えられる問題を直す (#2891): #2874 の -c features.daemon_auto_start=false はデーモンを「新しく起動しない」だけで、既に動いているデーモン(最初に起動したインスタンスの環境変数を持つ)にはつながるため、Codex_Sub(codex-3)の通知と返答が主インスタンス codex のものとして届いていた(codex-cli 0.157.1・2026-09-27 実測)。hook を注入する codex の起動行に --no-daemon(CODEX_NO_DAEMON_FLAG)を追加し、既存デーモンにもつながらず hook が各セッション自身の環境で動くようにした。未対応の codex に渡すと起動に失敗しうるため、codex --help の出力に --no-daemon があるときだけ付ける(codexSupportsNoDaemon。版番号は比べず、実行ファイルごとに 1 回だけ判定してキャッシュし、失敗・タイムアウトは付けない側)。-c features.daemon_auto_start=false は CommandMate の環境を持つデーモンを新しく作らないため残す。注入しない経路は従来どおり。修正・再ビルド後に codex のインスタンスを再起動すること(稼働中の codex は起動行が古いまま)。

  • fix(orchestrate): unit-related 判定で changelog.d/ を除外し不要な全テスト実行を防ぐ (#2889): classifyChanges の除外条件に changelog.d/ を追加し、ワーカーがコミットする CHANGELOG 断片によって unit-related ゲートが毎回 mode=full に切り替わる問題を解消

  • fix(worktree): スマホで ?view=chat の URL から「直接入力に切り替える」を押すとチャット表示に戻り直接入力キーボードが開かない (#2888): handleStuckSwitchToDirectInput が localStorage に terminal を書いて再マウントしても、再マウントした MobileTerminalTab の resolveSurfaceMode は URL の ?view=chat を localStorage より優先して読み直すため chat に戻っていた。新規 clearSurfaceModeParamFromLocation(src/config/surface-mode-config.ts)を writeSurfaceMode の前に呼び、window.location の view パラメータだけを他の query と hash を保ったまま history.replaceState で取り除くようにした。

  • fix(realtime): terminal_snapshot の push に promptAnswerable を乗せ、Send 無効化案内の遅延を無くす (#2887): #2870 で /current-output に足した promptAnswerable を WebSocket の push(emitTerminalSnapshot・snapshotFingerprint、src/lib/realtime/terminal-broadcast.ts)が運んでいなかったため、push が健在な間は値が変わっても再送されず、フォールバックポーリング(最大 15 秒)まで届かなかった。push のペイロードと TerminalSnapshotEvent(src/lib/realtime/types.ts)に promptAnswerable?: boolean を足し(undefined のときは項目を出さない)、useTerminalPanePolling は push が同キーを持つときだけ即座にその値を採用し、持たないとき(旧サーバ)は従来どおり同一ウインドウなら前の値を保持する。

  • fix(orchestrate): orchestrate-monitor が名前空間つきセッションに介入できない問題を直す (#2886): 介入先の tmux セッション名を capture --json の cliToolId から mcbd-${cliToolId}-${worktreeId} と組み立てていたため、サーバに名前空間(#2866)があると実際の名前は mcbd-{ns}-{cli}-{id} になり、権限プロンプトの自動承認や Enter の確定が存在しないペインへ送られ届かなくなっていた(NOT delivered)。buildCurrentOutput(GET /api/worktrees/:id/current-output と capture --json の元)の応答に、実際に使われているセッション名(CLIToolManager.getTool(cliToolId).getSessionName(worktreeId, instanceId)。旧形式の採用も反映)を sessionName として追加し(CLI 側の型 src/cli/types/api-responses.ts にも optional で追加)、monitor.sh はこれを優先して介入先を決める(無ければ従来どおり cliToolId から組み立て、--session-prefix 指定時は従来どおりそちらが優先)。skills 側(Kewton/commandmate-skills)への移植は Kewton/commandmate-skills#268 で別途行われ、その移植と .claude/skills/sync-map.json の pin 更新までは sync-map.test.ts が想定どおり赤。

  • fix(api): respond の decisionId 経路で、別の CommandMate サーバが作った同名 tmux セッションの画面を応答ポーリングで会話履歴に取り込まない (#2885): /api/worktrees/:id/respond の decisionId 経路(エージェントの API で承認を届ける経路)は、ルートの所有確認(#2865)より前で分岐するため確認を通らず、承認の配達後に startPolling が同名セッションの画面を capture して自サーバの履歴に保存しうった。配達後の後始末(structured-decision.ts の settleAnsweredDecision)で checkWorktreeSessionOwnership を呼び、foreign または worktree 行が無い(null)ときは startPolling とターミナルスナップショットの broadcast を行わず respond-decision-skip-polling-foreign-session を warn で出す。承認の配達結果(レスポンス)は変えず、owned と absent(ポーラー側が扱う)は従来どおりポーリングする。

  • fix(detection): codex 0.157.1 の trust ダイアログ(Trust this folder?)を認識し、起動時の自動承認と /prompt-response を効かせる (#2884): trust ダイアログの判定がすべて Do you trust(0.155.1 までの文言)に依存しており、文言を変えた 0.157.1 では getCodexActiveDialog / getCodexLifecycleDialog が 'trust' を返さず、起動時の自動承認が効かず evaluateDialogPresence('codex', …).present === false で /prompt-response も拒否していた。3 か所(CODEX_DIALOG_PATTERN・getCodexActiveDialog・getCodexLifecycleDialog)で使う共有定数 CODEX_TRUST_QUESTION_PATTERN(Do you trust と Trust this folder\? の両方を認める)を cli-patterns.ts に追加した。0.155.1 以前の判定は変わらない

  • fix(codex): codex 0.157 の共有デーモン経由で hook のインスタンスが取り違えられる問題を直す (#2874): codex 0.157 から TUI は既定でマシン共通の codex app-server --managed-daemon につながり、hook はデーモンのプロセス(最初に起動したインスタンスの環境変数)の中で動くため、別インスタンス・別 worktree の通知(返答・停止・許可確認)がすべて最初のインスタンスとして届いていた。hook を注入する codex の起動行に -c features.daemon_auto_start=false(CODEX_EMBEDDED_MODE_ARGS)を付け、codex を埋め込みモードで動かして hook が各セッション自身の環境で動くようにした(注入しない経路は従来どおり)。あわせて /api/hooks/agent-event は、codex の user_prompt_submit の通知先インスタンスの tmux セッションが無いとき agent-event-instance-not-running を warn で出す(応答は待たず、振り分け直しはしない)。既にデーモンにつながっている codex は取り違えたままなので、再ビルド後に codex のインスタンスを再起動すること。

  • fix(worktree): CommandMate から答えられないプロンプトのウインドウに Send を出さない (#2870): /prompt-response の再確認(ツール専用の読み取り → 汎用パーサー → ダイアログの存在確認 → 拒否理由の判定)を assessPromptAnswerability に切り出し、/current-output がそれと同じ判定で promptAnswerable を返すようにした(汎用パーサーで読んだ prompt のときだけ。isPromptWaiting・promptData・sessionStatus は変えない)。promptAnswerable: false のとき、PC の PromptPanel とスマホの MobilePromptSheet は選択肢を表示したまま Send と選択を無効にし、「この画面は CommandMate から操作できません。直接入力モードで操作してください。」と #2869 の「直接入力に切り替える」リンクを最初から出す。#2868 のように検出がずれたときに、押しても何も起きない Send が出なくなる。

  • fix(detection): codex 0.157.1 の /model 選択画面を選択リストとして判定する (#2868): 0.157.1 でフッタが enter select · esc back(2 段目の推論レベル選択は enter default · s session · esc back)に変わり、選択画面が multiple_choice と読まれて Send が prompt_no_longer_active で拒否されていた。フッタ行全体に一致する CODEX_PICKER_FOOTER_PATTERN / CODEX_EFFORT_PICKER_FOOTER_PATTERN を追加し、状態判定(分岐 0.8、窓の最後の行のみ)とダイアログ判定の入口で認めるようにした。CODEX_VERIFIED_AGAINST を 0.157.1 に更新

  • fix(tmux): 別の CommandMate サーバが作った同名 tmux セッションを、表示・送信・停止・採用の対象から外す (#2865): worktree ID はディレクトリ名から作られるため、DB もパスも別の 2 台のサーバが既定ソケット上で同じ mcbd-<cli>-<worktreeId> を解決し、片方の画面に他方のセッションが映り、送信・キー入力・停止・Auto-Yes の応答・起動時の rename まで他方のエージェントに届いていた。セッションの #{session_path} の realpath が worktree の path の realpath と一致するものだけを自分のものとし(新規 src/lib/tmux/session-ownership.ts。path が取れないセッションも安全側で他サーバ扱い)、一致しないセッションはサイドバー・状態表示では稼働扱いにせず、/api/worktrees/:id/* の送信・キャプチャ・キー入力・応答・中断・停止・タイマー登録は HTTP 409(code: 'session_owned_by_other_server'・sessionName・sessionPath)で拒否し、kill-session の複数対象では自分のものだけ停止して skippedForeignSessions を返す。既存セッションの採用(全 8 ツール)は ForeignSessionError、WebSocket のターミナル購読は terminal_error、Auto-Yes は応答せず warn、タイマー発火とリレー配送が通る sendUserMessage は送信も応答ポーリング開始もせず stage: 'foreign_session' を返し(worktree 行が無く所有を確認できない場合も同じ)、起動時 rename は skippedSessions に記録、worktree 削除時の停止は kill しない。tmux list-sessions は #{session_path} をタブ区切りの最終列で返すようになった。セッション名の変更(名前空間)は #2866 で行うため、同名が埋まっている worktree では自サーバ側のセッションを起動できない(409)。