v0.29.2
[0.29.2] - 2026-08-31
Highlight: 2026-08-25 の利用者報告 4 件(リロード時のサイドバー空表示・Verification の使い方が解らない・既定エージェントが固定・codex が更新できない)を Epic #2071 として一括で解消したリリース。あわせて、全テスト green のまま
Unit Testsを exit 1 にしていた未回収setTimeoutを 8 コンポーネントぶん回収した(無関係な PR の CI を 2 回赤にした実績があり、押下フィードバックとコピー確認の両方を共有フックへ一本化して再発経路を塞いだ)。DB マイグレーションなし。
Added
- feat(ui): Verification のゲート選択再実行・実行中ランの中止・履歴拡張・ゲートログ全文表示を追加 (#2063): 検証ペインは #1816 以来「全ゲートを実行する」以外に何もできず、
lintの 1 行修正を確かめるためだけに 1800 秒のbuildと mutex 付きのunit/integrationを道連れにするしかなかった。(1) ゲート選択:POST …/verifyは #1543 からgateIds(1..32)を受け付けていたのにverification-api.tsの呼び出しが{}しか送っておらず、型定義だけが在って死んでいた。サーバのplannedGateIds(組み込みゲート込み)をチェックボックスで選び、「不合格だったゲートだけ」ショートカットで直前ランの失敗集合(FAILING_GATE_STATUSES。skippedは含めない=断られたゲートはもう一度断られるだけ)をそのまま送れるようにした。全件選択はgateIdsを送らないことで表現する — 省略時の scope ゲートはimplicitで契約なしランの skip が赦されるのに対し、scopeを名指しするとexplicitになって skip が集計に効くため、両者は別のリクエストであり既定経路の挙動は 1 bit も変えていない。(2) 中止 API:POST /api/worktrees/:id/verify/runs/:runId/cancelを新設。gate-runnerに run 単位のキャンセルスイッチ(RunCancellation/cancelVerification())を入れ、実行中ゲートのプロセスグループへ SIGTERM→5 秒→SIGKILL を送ってから run をcancelledで閉じる(status 列だけ書き換えるとnpm run buildが worktree に居座ったまま UI だけ終わったことになる。ゲートは元からdetachedで spawn されているのでグループに届く)。中断されたゲートと未到達のゲートは理由つきのskipped行として記録し、aggregateRunStatusを通さず直接cancelledを返す(操作者の意図的な停止を「判定不能」と読ませない)。#2062 が残していたverification.runStatus.cancelledの生成元がこれで、verifying → cancel → cancelledという到達不能だった task 遷移もつながった。応答は 200(閉じた)/ 202(シグナル済みで収束待ち)/ 409(既に判定済み、またはこのプロセスが実行していない孤児)に分けている。(3) 履歴:DEFAULT_RUN_LIST_LIMIT = 10固定だったランキャッシュに「もっと見る」(route の上限 100 まで 10 件ずつ)を付け、#1593 から Web が一度も読んでいなかったGET /api/verification/runs(worktree 横断履歴)を折りたたみブロックとして読むようにした(開いたときだけ取得。1 ブランチで赤いゲートは作業への判定だが 6 ブランチで赤いゲートはゲートへの判定、という比較が verify.yaml 調整の実際の問い)。(4) ログ全文: 行の末尾 40 行表示に加えて、保存されているlog_tail全量をモーダルで表示しコピーできるようにした(「全文はcommandmate verify show」という端末への送り返しが、判定をスマホで読むための画面から出ていた)。保存されているのはoptions.maxLogTailBytesで切られた末尾だけであることをモーダル自身が明示する。実プロセスを使う統合テスト(tests/integration/verification-cancel-2063.test.ts)でゲートの shell とその背景子プロセス双方の pid が消えることを実測し、プロセスグループへ送らない変異では赤になることを確認済み。 - feat(ui): エージェントペインに「この構成を既定にする」と「未変更のブランチにも適用する」を追加 (#2067): #2065 は新規ブランチの既定エージェントを設定可能にし、#2066 はリポジトリ単位の宣言を足したが、どちらも「すでにあるブランチ」には触らない設計だったため、1 か月動かした環境では全ブランチが古いタブ順のまま残り、直す手段は 1 本ずつ開いて並べ替えることしか無かった(CHANGELOG #1516 が「既存分を揃えるマイグレーションは別 Issue」と書いたその Issue が未作成だった)。アクティビティバー「エージェント」のペイン末尾に開閉式パネルを追加し、いま並んでいるロスターのツール順(同一ツールの複数インスタンスは 1 つに畳む)を #2065 の
PUT /api/settings/default-agentsにそのまま書き込む「既定にする」と、GET/POST /api/worktrees/apply-default-agentsで未変更のブランチにだけ書き込む一括適用を置いた。「未変更」はworktrees.selected_agents IS NULLかつagent_instancesの行が無いことで、これは #2066 が repo 宣言を差し止めるときに見るのと同じ 2 条件・同じヘルパ(getUnchangedAgentWorktreeIds())であり、手で並べ替えたブランチも別名を付けたブランチも書き換わらない。適用は確認ダイアログの手前で件数を表示し、クリック時点で件数を読み直してからダイアログに出すので、表示件数と実際の更新件数(サーバが返すupdated)が食い違わない。パネルは開くまで 1 リクエストも出さない(#2054 の「claude / codex だけのロスターはゼロリクエスト」条件を維持)。あわせてActivityPaneの ErrorBoundary 名がagentに対して実体と違う'AgentSettingsPane'(モバイルの checkbox ペイン)を報告していたのを'AgentInstancesPane'に修正した。 - feat(repo-config): リポジトリ単位の既定エージェントを
.commandmate/agents.yamlで宣言できるようにする (#2066): #2065 が入れたresolveSelectedAgents()のSELECTED_AGENTS_LAYERSにはrepo層が宣言だけされていて値が来ていなかった(['worktree','repo','appSettings'])。#2066 はその 1 層に値を供給しただけで、解決関数の本体も層の順序も組み替えていない — 優先順位はworktree -> repo ファイル -> app_settings -> 定数になり、値を渡すのはsrc/lib/db/worktree-db.tsのgetWorktrees()/getWorktreeById()の 2 箇所だけ(getWorktrees()はrepository_pathごとに 1 回だけ解決する)。宣言はagents: [codex, claude](任意でprimary:)で、検証はvalidateAgentsPair()を共用(2〜6 個・重複なし・CLI_TOOL_IDSのいずれか=app_settingsと同一の制約)。壊れている/値が不正なときは例外を投げずrepo-agents:*の警告ログを出して次の層に落ちる(fail-open。空ファイルとコメントのみのファイルは「宣言していない」と読んで無音)。ファイルはscanWorktrees()がgit worktree listの成功後にrefreshRepoAgentsConfig()を呼ぶ形で同期のたびに読み直し、getWorktrees()自身はメモリ(TTL 60 秒)を見るだけにした — サイドバーのポーリング経路にファイルシステムプローブを生やさないという #1913 の規則を守るためで、否定的な答えもキャッシュするので壊れた宣言の警告はポーリングごとではなく同期ごとに 1 回になる。agent_instancesを既に持つ worktree には宣言を適用しない(ロスターだけでなくselectedAgentsにも):/sessionsと Review タブはwt.selectedAgentsを読んでagentInstancesを見ないため、これが無いと「タブは変わらないのにチップだけ宣言どおりに描き変わり、実際に動いているエージェントが稼働中一覧から消える」。PATCH /api/worktrees/[id]はagentInstancesとselectedAgentsを独立した分岐で書くので、この状態は日常的に発生する。CMATE.mdにキーを置く案は採らなかった(CMATE.mdは worktree ルートから読む=リポジトリのロスターが存在する前に読めない/Markdown の表なので順序つきリスト+primary を 1 セルに押し込むことになる/parseCmateConfig()はスケジュール実行経路なので巻き添えが大きい)。設計はdocs/design/repo-agents-config.md。 - feat(agents): エージェント CLI の版表示と、pane の外で走る codex 更新導線を追加 (#2069): CommandMate には CLI の版表示も更新導線も無く、
src/cli/config/cli-dependencies.tsには codex の行すら無かった。commandmate agents versions/agents update <tool>・More 画面の「設定」・エージェント一覧ペインの 3 面から更新できるようにし、更新はエージェントの tmux pane ではなく CommandMate 自身の子プロセスで実行して出力をそのままストリームする(codex の「Update now」は codex を終了させてからインストーラを動かし自動再起動しないため、pane 内で走らせると pane が素のシェルに落ちる=#2070 の状態になる)。実行コマンドは codex 0.149.0 以降ならcodex update、未満または codex が PATH に無ければnpm install -g @openai/codex@latestで、いずれもexecFileに argv 配列を渡し(シェル文字列連結なし)、findExecutableOnPath()で絶対パスへ解決してから起動し、env はsanitizeEnvForChildProcess()を通す(PATH は起動シェル由来のまま)。リクエスト本文が触れるのはツール ID 1 個だけで、それはUPDATABLE_AGENT_TOOLSで検証したあと捨てられる(argv はモジュール内のリテラル)。「更新あり」はネットワークを使わず codex 自身が書く~/.codex/version.json(latest_version/dismissed_version)を読み、他ツールは--versionの installed 版のみを表示する。ファイルが無い/壊れている/型が違う場合は fail-open で「更新情報なし」になる。対象ツールのセッションが稼働中なら「再起動が必要」を警告し、インスタンス単位の再起動ボタン(kill-session)を出す。cli-dependencies.tsに Codex CLI(codex --version, required: false)を追加したのでcommandmate init/statusが codex の版を表示する。隔離環境実測(2026-08-31, 隔離 HOME + 隔離 npm prefix +tmux -L cm2069):agents update codex --yesでcodex updateが走り 0.149.0 → 0.151.0、その間に走らせていた codex TUI の pane は pane_pid 38581 のまま無傷、利用者のグローバル codex(Homebrew 0.149.1)は未変更。 - feat(settings): 新規ブランチの既定エージェントを More 画面から設定できるようにした (#2065): 新しく発見された worktree のタブは
DEFAULT_SELECTED_AGENTS = ['claude','codex','antigravity']というコンパイル時定数からしか作られなかった(upsertWorktreeはselected_agentsを書かず、scan/sync はagent_instancesも作らないので、新規 worktree は必ず定数の順序・定数の primary になる)。app_settings.default_selected_agents(2〜6 件・CLI_TOOL_IDS内・重複なし・先頭が primary)とGET/PUT /api/settings/default-agentsを新設し、More 画面に順序変更つきの設定カード(インストール済み CLI を併記。isInstalled()の子プロセス起動は?include=installedを付けたこの画面だけが払い、30 秒 TTL + single-flight でキャッシュする=#1913 の「ホットパスで await しない」規約)を追加した。フォールバックの順序はresolveSelectedAgents()1 箇所に集約し、worktree -> repo -> app_settings -> 定数の層を配列で宣言する(repo層は #2066 が値を渡すだけで挿さるよう宣言だけ済ませ、本 Issue では実装しない)。サーバ側はparseSelectedAgents(raw, appSettingsDefault)とresolveAgentInstances()の 2 経路が、クライアント側は 8 箇所のフォールバック(src/types/sidebar.ts×2 /src/app/sessions/page.tsx/src/components/review/ReviewTab.tsx/src/hooks/useWorktreeDetailController.ts×4)が同じ解決関数を通る。設定が無い環境の挙動は定数のままで不変、既存 worktree のagent_instances行も書き換えない(agent_instancesがあればそれが権威で、早期 return はそのまま)。GET /api/worktreesはdefaultSelectedAgentsを無条件で載せ、/api/capabilitiesにはトークンdefault-selected-agentsのみを足す(値は載せない — この応答は認証前に読めうる面なので、#1925 / DR4-008 のとおりインストール構成を一切反映しないコンパイル時固定リストのまま)。 - feat(verification): Verification ペインに 4 状態のオンボーディングと
verify.yaml起案導線を追加 (#2061): ペインは.commandmate/verify.yamlを読んでいなかったため、ゲートを 1 件も宣言していないリポジトリと「宣言はあるが 1 度も走らせていない」リポジトリが画面上で同一で、未作成のまま「再検証」を押した唯一の手がかりは失敗した run のconfigゲートに入る英文 1 行(.commandmate/verify.yaml not found in …)だけだった。読み取り APIGET /api/worktrees/:id/verify/config(existsとerrorは独立 —— 在って壊れている状態は 200 +exists:true+errorで返し、「宣言してください」と言われた操作者が 2 本目のファイルを書きに行くのを防ぐ)を新設し、ペイン先頭に 2 行の説明と 未作成 / 宣言済み・未実行 / 実行中 / 結果 の 4 状態(+読み取り未着のunknown)を別の文言・別の CTA で描画する(tests/unit/components/worktree/VerificationPaneOnboarding.test.tsxがスナップショットで固定)。起案はcommandmate verify init(--dry-run/--json/--cwd)と Web の「CI から起案する」ボタンがsrc/lib/verification/verify-draft.tsの同一実装を呼ぶ ——.github/workflows/*.ymlの各run:とpackage.jsonのscriptsを読み、何度でも安全に再実行できるコマンドだけをゲートにして、拒否したものは 14 種の理由つきで報告する(本リポジトリの CI からは lint / typecheck / unit / build を含む 11 ゲートを起案し、npm ci/npm audit/npm publish/test:e2e/ watch モードのnpm testは拒否する)。既存ファイルは決して上書きしない(--forceは用意せず、書き込みはflag:'wx')。verify initは verify 系で唯一サーバを必要としない —— verify.yaml がまだ無い段階で走らせるコマンドなので、commandmate startを前提にすると起動の前提がその起動自身になる。空状態の CTA はcommandmate verify <worktree-id>という補間されない literal だったので、実際の worktree id を補間するようにした。
Changed
- fix(ui): 検証ペインの判定語彙を日本語化し、判定の意味・CLI exit code・実行されなかった理由を画面に出す (#2062):
verification.runStatus.*/verification.gateStatus.*は DB の生トークン(passed/not_started/SKIP/TIMEOUT)を ja と en で同一値のまま表示しており、日本語表示は事実上未翻訳、かつどちらの言語でも語の意味が画面のどこにも無かった。判定語を各言語の語(合格 / 不合格 / 未着手 / 判定不能、Passed / Failed / Not started / Could not judge)に置き換え、判定ごとに 1 行の gloss(verification.runStatusGloss.*/gateStatusGloss.*)を追加。not_startedは「作業証跡ゼロ: コミットも未コミット変更も無い」= CLI が exit 21 を返す判定であって不合格ではないことを明示する。CLI との対応は綴りの一致ではなく明示された exit code で示す方式に変更し、検証ラン節にRUN_STATUS_EXIT_CODEから組み立てた凡例(合格 = exit 0 / 不合格 = exit 20 / 未着手 = exit 21 / 判定不能 = exit 99)を、各ラン行にCLI exit=<code>を出す。aggregateRunStatusがskippedを 1 件でも含むランをerrorに昇格させる仕様のため、options.skipInPrimaryCheckout(サーバープロセスの作業ディレクトリでコマンド系ゲートを断る保護)が原因不明の赤いラン=製品の不具合に見えていたので、選択中ランの判定バナーが実行されなかったゲート名と理由(primary-checkout / work-evidence 落ち / mutex 待ち / 契約未接続 / 契約なし / requireScopeClean:false / 不明)を列挙する。組み込みゲートwork-evidence/scope/env-clean/configは生 id のままだったのでゲート行に説明文を、[contract]マーカーには意味の注記を追加。verification.runStatus.cancelledはキーを残したまま文言だけ整えた(生成元は #2063 のキャンセル API が入るまで存在しない)。判定語の合意はテストで固定(生トークンの再発と ja==en の再発をtests/unit/i18n/verification-vocabulary-2062.test.tsが、CLI exit code の写しの drift をtests/unit/verification/run-verdict-vocabulary-2062.test.tsが、docs/UI_UX_GUIDE.mdの Activity 一覧とACTIVITIESの一致をtests/unit/docs/ui-ux-guide-activities-2062.test.tsが禁じる)。あわせてdocs/UI_UX_GUIDE.mdの Activity 一覧を 6 項目からsrc/config/activity-bar-config.tsの 10 項目へ更新(todo#1015 /skills#1441 /verification#1816 /env#1968 が未反映だった)。 - feat(cli-tools): codex の更新ダイアログへの応答を方針化し、既定を「次の版まで出さない」に変更 (#2068): CommandMate は codex の
Update available!ダイアログに Issue #890 以来つねに2(Skip)を送っていたが、2は何も記録しない — codex-cli 0.149.1 を隔離CODEX_HOMEで実測したところ(2026-08-31)、2はversion.jsonのdismissed_versionをnullのまま残し、次回起動でまた同じダイアログが出る。結果として毎回の起動でダイアログが出て毎回サーバーが勝手に閉じるため、利用者は CommandMate 経由で更新を選べなかった。応答方針をCM_CODEX_UPDATE_DIALOGで選べるようにし(skip=2/skip-until-next-version=3(既定) /update=1/ask=送らない)、既定を3に変更した(実測:dismissed_version: "0.151.0"が書かれ、次回起動でダイアログは出ない)。updateは codex がnpm install -g @openai/codexに置き換わって終了したあと、同じ pane へ起動コマンドを送り直す(#2070 の「シェルに落ちた pane」機構に乗るが、判定はjudgeToolLivenessではなく新設のfindShellPromptTail—npm installの出力が 3 行しかないため、死んだ› 1. Update now行が #2070 の 12 行 alive 窓に残って exit を打ち消すことを実測したため)。askはwaitForReadyが自動応答せずポーリングを続け、ダイアログはdetectPromptが 3 択のmultiple_choiceとして報告しているので PromptPanel でそのまま人が選べる。Auto-Yes は全方針で従来どおりこのダイアログに答えない(Issue #1829 のガードを維持、askを含めて回帰テスト済み)。認識できない値は既定にフォールバックする(updatesがupdateとして解釈されることはない)。claude / opencode / copilot / gemini / antigravity の挙動は不変。
Fixed
- fix(ui): コピー確認フィードバックの未回収タイマーを共有フックに一本化して回収する (#2180): コピー確認(
COPY_FEEDBACK_RESET_MS=2000ms)のsetTimeoutを id を持たないまま張っていた 3 ファイル(MarkdownEditor/FileViewerの content・path 2 系統 /review/ReportTab)で、コピー直後 2 秒以内に unmount するとコールバックが消えたツリーへsetCopied(false)を書きに行っていた。ブラウザでは無害(React が torn-down root への更新を捨てる)だが jsdom ではwindow撤去後に発火し、全テスト green のままUnit Testsが exit 1 になる — 落ちるのは当該ファイルを差分に含まない無関係な PR で、押下フィードバック側の同型(#2174)は実際に PR #2170 / PR #2173 を赤にしている。窓が 150ms ではなく 2000ms のぶんコピー側の方が踏まれやすい。#2176 のuseKeyPressFeedbackにならってsrc/hooks/useCopyFeedback.tsを新設し(id をuseRefに持ち、次のコピー(前回の残り時間を引き継がず張り直す)・reset()・unmount の 3 箇所でclearTimeout)、既にpathTimerRef/contentTimerRefで自前回収していたWorktreeDetailSubComponents(path / repo path)とFilePanelContent(path / content)も含む 5 ファイル 8 ボタンすべてを移行した。1 コンポーネントに 2 系統ある画面(FileViewer/WorktreeInfoFields/FileToolbar)はフックをボタンごとに 1 インスタンス呼ぶので、片方を押しても他方の確認表示は自分の 2 秒で消える。見た目は不変(クリップボード書き込み解決と同時にチェックマークが出てCOPY_FEEDBACK_RESET_MS後に戻る)。変異注入で検証済み(unmount のclearTimeout削除で 8 テスト・再 arm のclearTimeout削除で 6 テスト・resetのclearTimeout削除で 1 テスト・FileViewerの 2 インスタンスを 1 本に畳むと 2 テストが赤)。併せてui-feedback-config.tsの陳腐化したSite:コメント 3 件(KEY_PRESS_FEEDBACK_RESET_MSは #2176 以降useKeyPressFeedbackが唯一の import 元、NAV_KEY_REFRESH_DELAY_MSはuseSpecialKeys/UnsentComposerBar)を実測に合わせた。 - fix(ui): 押下フィードバックの未回収タイマーを 3 コンポーネントぶん回収し、共有フック
useKeyPressFeedbackに一本化 (#2176):TerminalEscapeHatch/NavigationButtonsに、#2174(PR #2175)がOpencodeQuickKeysで直したのと同型の未回収setTimeoutが残っていた(どちらもclearTimeoutもuseRefも持たず、押下からKEY_PRESS_FEEDBACK_RESET_MS=150ms 以内に unmount するとコールバックが消えたツリーへsetActiveKey(null)を書きに行く)。3 つともuseSpecialKeystransport は共有するがタイマーは各ファイルが個別に持っていたため #2175 の修正は届いていない。ブラウザでは無害(React が torn-down root への更新を捨てる)だが jsdom ではwindow撤去後に発火し、全テスト green のままUnit Testsが exit 1 になる — しかも落ちるのは当該ファイルを差分に含まない無関係な PR(#2174 は実際に PR #2170 / PR #2173 を赤にした)。修正を 2 回コピーする代わりに、タイマー id をuseRefに持ち次の押下(前回の残り時間を引き継がず張り直す)と unmount の両方でclearTimeoutする実装をsrc/hooks/useKeyPressFeedback.tsへ抽出し、OpencodeQuickKeysを含む 3 ファイルすべてを移行した(4 つ目のキー列を足す人が同じ穴を開けないため)。フックは押下ハイライトだけを所有しキー送出には触れないのでuseSpecialKeysの挙動は不変。見た目も不変(押下で同期的に点灯しKEY_PRESS_FEEDBACK_RESET_MS後にきっかり消灯)。押下直後 unmount で Unhandled Error が出ないことを 2 ファイル+フック単体で固定し、clearTimeout2 箇所それぞれの除去で赤くなることを変異注入で確認済み(unmount 側の除去で 9 件、再 arm 側の除去で 8 件が赤)。 - fix(ui): opencode クイックキーの押下フィードバックタイマーを回収する (#2174):
OpencodeQuickKeysのhandleClickがsetTimeout(() => setActiveId(null), KEY_PRESS_FEEDBACK_RESET_MS)を id を持たずに撃っており、次の押下でも unmount でもclearTimeoutされなかった。押下から 150ms 以内に pane が消えると、コールバックは消えた React ツリーへsetActiveId(null)を書きに行く。ブラウザでは無害(React が捨てる)だが jsdom では違い、タイマーが自分を仕込んだテストより長生きするとwindowを撤去済みの環境で発火し、たまたま走っている別テストの Unhandled Error として現れる。タイマー id をuseRefに持たせ、次の押下(前回の残り時間を引き継がず 0 から張り直す)と unmount の両方でclearTimeoutする形に変更した。作法は同じツリーの他の一時フィードバックタイマー(CopyButton/TruncationTooltip/MemoCard)に揃えてあり、見た目は不変 — ハイライトは押下と同時に付き、KEY_PRESS_FEEDBACK_RESET_MS(150ms)後にちょうど消える。混入元は Issue 本文が挙げる #2131 (PR #2160) ではなく、strip 自体を追加した #2046 (ceb1059d):git log -1 -- <file>はファイルの最終コミットを返すため #2160 に見えるが、git blameを当該行に打つとceb1059dが出る。同型の未回収タイマーはTerminalEscapeHatchとNavigationButtonsにも残っているが、いずれも #2174 の scope 外のため本 PR では触っていない。 - fix(session): tmux セッションだけが残りツールが終了した pane を「稼働中」と報告し、次の
sendが復旧不能になる問題を修正 (#2070): 「セッションは在るがツールが落ちた」検出はworktree-status-helper.tsのcliToolId === 'claude'分岐 1 箇所に閉じていたため、codex の「Update now」(codex をnpm installに置き換えて終了する)・Ctrl+C二度押し・クラッシュで pane が素のシェルに戻ってもhas-sessionは yes を返し続け、サイドバーは緑のまま・startSessionは既存セッションを見て何もせず・sendはwaitForPromptがタイムアウトして手動kill-session以外に復旧手段が無かった。判定をICLITool.livenessSpec()(ToolLivenessSpec:ツール自身の prompt-ready パターンの否定+シェルプロンプトの肯定という連言)へ移し、共有ルールjudgeToolLiveness()を claude / codex / copilot / opencode / gemini / antigravity / vibe-local の 7 ツールに広げた上で、各ツールのlaunchSession()再利用経路が「ツールが動いていない」と 2 回続けて読めたときに同じ pane へ起動コマンドを再送し、sendMessage()冒頭でも同じ復旧を行うようにした(opencode は死んだ port と subscription をreleaseOpencodeEventStream()で手放してから再割当てする)。claude の既存判定は値・分岐順序・reason 文字列まで不変で、isSessionHealthy()はその spec への委譲になっている。aliveTailLines(末尾 12 行に限定)とshellPromptPatterns(user@host … %の肯定形)が両方必要なことは実測に基づく: codex 0.149.1 の終了済み pane には trust ダイアログの› 1. Yes, continueが約 1000 行上に残っており全画面検査では永久に alive になり、その zsh プロンプトはちょうど 40 文字=claude のMAX_SHELL_PROMPT_LENGTHと同値で長さゲートも素通りする(2026-08-31 / 専用 tmux socket / 200x1000 実測、tests/fixtures/tool-liveness-2070/)。サイドバーとcommandmate lsには理由コードexitedを additive に追加し(既存 reason の値・意味は不変)、idle(起動していない)とidle (exited)(落ちた)を区別できるようにした。 - fix(ui): Verification の入口を契約なしのブランチにも出し、理由をタッチで読めるようにした (#2064): ヘッダの
VerificationStatusChipがif (!task) return nullで契約を送っていないブランチでは丸ごと消えていたため、Verification を知らない人ほど入口に辿り着けなかった問題を修正。task 行が無いときはペイン名(verification.title)+RESULTバッジ「未検証」を描き、クリックでこれまで通りペインを開く(run が無いときのバッジも従来の—から「未検証」に変更)。判定理由はこれまでaria-label/titleにしか無くタッチ端末からは読めなかったので、チップ右端に ⓘ トグルを足して同じ文言をポップオーバーに出す(Escape / 外側タップ / ペインを開く操作で閉じる。hover-reveal は使っていないのでマウスとタッチで見えるものが一致する)。不合格ゲート名は両シェルとも「ペインで選択中の run が最新 run と同じとき」だけ渡される値だったため古い run を選ぶとヘッダから消えていたが、チップ側で run id をキーに直前の行を保持するようにして解消(新しい run が最新になったら破棄するので、前 run の失敗を新 run の verdict の下に出すことはない)。あわせてモバイル Tools タブのverificationサブタブがverificationstate 未指定時に消える条件を撤去し、NotesAndLogsPane/MobileContentのverificationprop を必須化して、常時表示の PC Activity Bar と到達可否が型で一致するようにした。