Skip to content

cross-review: 再開すると --only / --max-rounds / --verify-command などの引数が黙って無視される #648

Description

@takemi-ohama

何を見つけたか

/ndf:cross-review を中断・再開すると、state.py init に渡した --only /
--max-rounds / --rotate-after / --verify-command / --verify-exit-code /
--host黙って無視される。状態ファイルが残っている限り再開経路へ入り、
これらの値は最初に init したときのものが使われ続ける。

実測(PR #646 の実装レビューで踏んだ):

  1. state.py init 646 --host claude で開始(onlynull
  2. 特定のランタイムだけでレビューを回したくなり、--only codex を付けて
    init をやり直した
  3. 状態ファイルが残っているため再開経路へ入り、onlynull のまま。
    start-round はこれまでどおり輪番の 2 者を返した

出力にも「指定した値を採らなかった」旨は出ない。↻ 追加レビュー観点を state に 反映して再開 は追加レビュー観点だけを指しており、他の引数を無視したことは
どこにも現れない。

どこで見つけたか

plugins/ndf/skills/cross-review/scripts/state.py_resume_from_state():1540。呼び出しは :1644 の 1 か所)。

_resume_from_state(pr, repo, worktree, manual_extra_review) は引数の名前空間(args)を受け取らないため、
init に渡した引数を反映する手段が無い。再開経路が状態ファイルへ書き戻すのは次の項目だけである。

  • changed_files / auto_review_categories / auto_review_instructions
    (古い状態ファイルのとき)
  • manual_extra_review_instructions / extra_review_instructions
    --focus / --extra-instructions-file を渡したとき)
  • review_instructions
  • carried_over

only / max_rounds / rotate_after / host / verify_commands /
verify_exit_codes はいずれも書き戻されない。_init_new_state() が状態を
組み立てるときにしか読まれない。build_parser の既定値は --max-rounds 12 /
--rotate-after 8state.py:4352-4353)である。

cross-refactoring にも同じ形がある。 plugins/ndf/skills/cross-refactoring/scripts/refactor_lib/commands/setup.py:305-312
は、状態ファイルがあり final が決まっていなければ、投稿の扱い(_apply_post_event)だけを
反映して return する。refactor.pyinit が受ける --max-outer-rounds:108、既定 3)/
--max-test-rounds:112)/ --max-fix-rounds:117)/ --max-items-per-round:119)/
--model:127)は再開時に反映されない。無視したことは出力に出ない(出るのは
↻ 前回中断した状態から再開します だけ)。

共通層 plugins/ndf/scripts/lib/statefile.pyload / save / emit / die / info と保存の後の
処理の登録を持ち、cross-refactoring が状態ファイルの読み書きに使う(cross-reviewstate.py
使っていない。grep -c 'statefile\.' state.py は 0)。再開時に引数を状態へ重ねる手順は、どちらの
Skill にも共通層にも無い。

なぜこの変更の範囲外なのか

PR #646(issue #495)の受け入れ条件は AC11〜AC31 で、対象は
.ndf/worktree.local.json を共有の宣言へ重ねる仕組みである。cross-review の
Skill そのものの引数の扱いは、受け入れ条件にも計画の「やらないこと」にも
含まれない。

直さないと何が起きるか

  • 引数で進め方を変えられない。 長丁場の収束ループで担当を絞る、上限を
    伸ばす、実行検証を後から足す、といった調整が効かない。効かなかったことも
    出力に出ないため、進行側は指定が通ったものとして読む
  • 回避するには状態ファイルを手で書き換えるか、退避して作り直すしかない。
    作り直すとラウンド数・引き継いだ指摘・待ち行列・却下の記録が失われる
  • --only の指定が通っていないことに気づかないまま進むと、外したはずの
    ランタイムが起動する。打ち切りが分かっているランタイムを避けたい場面
    (agy が 4 回連続 STALLED になった rf646 の実績)で、避けたつもりで
    避けられない
  • シェル側の $ONLY と状態ファイルの only がずれ、起動し直しで外したつもりのランタイムが起動する。
    SKILL.md の骨組みは起動・監視・取り込みをシェル変数の $ONLY で絞る(SKILL.md:229(起動)、
    :236(監視)、:239(取り込み)、:249(根拠の検証))。
    一方、判定は状態ファイルの only で担当を決める(_round_reviewersstate.py:1893-1916)。
    再開時に ONLY=codex を渡し、状態ファイルの onlynull のままだと次のように進む。
    1. 輪番の担当が agy + kiro のラウンドでは、$ONLY の絞り込みで誰も起動しない
    2. judge は agy と kiro を結果なしとして扱い、RELAUNCH_AGENTS='agy kiro'(終了コード 7)を返す
    3. 起動し直しの分岐(SKILL.md:255-261)は $ONLY で絞らないため、避けたかった agy を起動する。
      この分岐は launch-reviewer.shbg-wait.sh runmonitor.py --phase review --agents "$RELAUNCH_AGENTS_CSV")→ bg-wait.sh waitread-result の順で回り、どの段でも $ONLY
      見ない
      (監視へ渡す担当は judge が返した RELAUNCH_AGENTS_CSV である)

修正レイヤー

plugins/ndf/scripts/lib/statefile.py に置く、再開時の引数の反映の契約。「明示的に渡された引数
だけを状態へ重ね、反映しない引数は渡されたら知らせる」を 1 か所で持つ。現れている cross-review
_resume_from_state だけを直すと、同じ形を持つ cross-refactoring の再開経路(setup.py:305-312)が
残り、cross-review--onlycross-refactoring の上限・モデルの指定で反映の規則が食い違う。

採る手

  • 新設: 渡されたかどうかの区別と、反映しない引数の知らせ方を持つ再開時の反映を statefile.py に作る
  • 統合(consolidate_duplication): cross-review_resume_from_statecross-refactoringinit の再開経路が、その反映を使う

回避策

状態ファイルの only を書き換える。 _round_reviewersonly を最初に見るため、後のラウンドにも効く。

rounds[-1].reviewers の書き換えは開いているラウンドにしか効かない。次の start-round で輪番へ戻る。

提案

両 Skill の再開経路で、明示的に渡された引数だけを状態ファイルへ反映する(上の「修正レイヤー」の契約)。
既定値のまま渡された引数で上書きすると、再開のたびに --max-rounds
12 へ戻る。argparse の既定を None にして「渡されたかどうか」を
区別できる形にするのが最小の変更である。

反映できないと決めた引数(例えば --host。母集合が変わると輪番の再現性が
崩れる)は、渡されたら 1 行知らせる。黙って捨てない。

由来

PR #646

関連

進行

モード: standard / 作業ツリー: .worktrees/feat/issue-727-participants / 計画: issues/issue-727-p6-participants-plan.md

  • 要求と受け入れ条件 — 2026-09-15 12:46
  • 作業場所の用意 — 2026-09-15 12:45
  • 設計 — 2026-09-15 14:11
  • 素材の収集と出典の確定
  • ドキュメント再構成 — 2026-09-15 14:11
  • ドキュメントレビュー — 2026-09-15 14:11
  • 計画 — 2026-09-19 19:41
  • 実装 — 2026-09-19 19:44
  • 構造改善 — 2026-09-21 20:59
  • 実装レビュー — 2026-09-22 02:12
  • 完了判定 — 2026-09-22 03:08
  • Pull Request — 2026-09-19 20:44
  • 確定仕様化 — 2026-09-22 03:43
  • 後片付け — 2026-09-22 03:30
  • 配布 — 2026-09-22 14:17
  • 体裁レビュー
  • リリース後テスト — 2026-09-22 15:06
  • 振り返り — 2026-09-22 15:38

閉じた理由

PR #793 #800 で直り、ndf 10.16.0(2026-09-22、main / タグ ndf--v10.16.0、PR #810)で配布した。リリース後テスト(#810 (comment) )でこの課題の受け入れ条件はすべて合格した。

振り返り: #810 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ndf-skillNDF の Skill 本体bugSomething isn't workingpriority: high実害・安全機構の欠落など、優先して対応する

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions