Skip to content

cross-refactoring: 実装担当が停止し続ける項目で適用ラウンドが無限に再試行される #647

Description

@takemi-ohama

事象

/ndf:cross-refactoring で、同じ適用ラウンド(書き換えるファイルが重ならない改善項目の群)の適用が上限なしに再試行される。後ろの群へ順番が回らず、収束の判定と最終ゲートへ届かない。

実測は 4 回ある。

rf646(PR #646):

  1. 実装担当の停止: 構造改善ラウンド 2 の項目 R4-004 で agy が 4 回続けて STALLED(480 秒無進捗で打ち切り)になり、--- 適用ラウンド 4 / 5 (実装 agy / 項目 R4-004)--- が繰り返された。進行側は停止したまま先へ進まない
  2. 週次上限の 429: 同じ日の早い時点では、claude が api_error_status: 429 で 15 秒で落ちる状態が続き、同じ項目(R3-001)の再試行が 3729 回記録された(grep -c 'api_error_status.:429'

PR #535(提案ラウンド 3): 適用ラウンド 1 / 4(実装 agy / 項目 R5-001) が 4 回続けて返り、後ろの群にあった R5-002 / R5-003 / R5-004 の 3 件は順番が来ないまま実行が終わった。状態ファイルの群は status: pending のままだった。

--- 適用ラウンド 1 / 4 (実装 agy / 項目 R5-001)---
--- 適用ラウンド 1 / 4 (実装 agy / 項目 R5-001)---
(以下 2 回同じ)
{"apply_round": 1, "impl": "agy", "items": ["R5-001"], "status": "pending",
 "base_sha": "08f7ba0c...", "head_sha": null, "fix_rounds": 0}

devbasex/devbase PR #191(issue #189): 範囲外の項目(R2-001)の適用を止めるため実装担当(kiro)のプロセスを停止したところ、monitor は NO_RESULT を返したが merge-apply の終了コードが 2 ではなく、SKILL.mdrf merge-apply ... || continue により next-apply-round が同じ群を再び返して kiro が再起動された。進行ごと停止して回避した。

PR #757#657 の中断と再開、v10.15.0): kiro が適用フェーズで 15 秒で終わり kiro-apply-r1-result.json を残さない → merge-apply が 2 を返し、駆動側の continuenext-apply-round へ戻る → 同じ群(適用ラウンド 3 / 3、項目 R1-004)を再び開く。29 回繰り返した時点で手で止めた。 繰り返しの回数に上限が無いため、止めるまで CLI の起動が続く。この回は残る担当(codex 1 者)で回し直した。

原因

群の状態を進めずに抜ける経路が 3 か所ある

merge-applyscripts/refactor_lib/commands/apply.pycmd_merge_apply)が取り込みの前に終了コード 2 で抜けると、群の statuspending のまま残る。next-apply-round(同 cmd_next_apply_roundapply.py:296-298)は pendingapplied の群を無条件に返すため、次の繰り返しで同じ群が開き直される。

経路 場所 起きる場面
結果ファイルが無い・JSON として読めない・JSON オブジェクトでない scripts/refactor_lib/gitfacts.pyread_result:1088-1100 実装担当が STALLED / TIMEOUT / 429 などで結果を残さずに終わった
着手前のテストが成功と確認できていない apply.py_load_apply_context:460-466 着手前テストの状態が green でない
適用の範囲が確定しない apply.py_load_apply_context:475-483 起点から HEAD までのコミットを列挙できない

後の 2 つは項目を blocked にするが、群の status は書き換えない。

向きの問題として、下位の読み取り関数が進行の終了コードを決めている。 gitfacts.read_result:1082)は結果ファイルが無い・読めないときに die(..., code=2):1089 / :1093 / :1096)でプロセスを終わらせるため、呼び出し元の cmd_merge_apply が群の状態を書く機会が無い。

取り込んだ後に取り消す経路(例: 必須トレーラーの欠落)は群を status: dropped にし、次の群へ進む。止まるのは取り込みの前に抜ける 3 か所だけである。PR #535 では、同じ実行の別のラウンド(取り消しの理由は「申告したコミットが実体として無い」「トレーラーの欠落」)は取り消しの後に次の群へ進んでおり、この違いと合う。止まった回の担当は agy で、結果ファイルが無い経路と読める。

再現(本物の tests/conftest.pytest_merge_apply.py の補助を使い、cmd_next_apply_roundcmd_merge_apply を直接呼んだ。群は 2 つ、1 つ目の担当は agy、結果ファイルは置かない):

❌ agy の結果ファイルがありません: …/.cross_refactoring/agy-apply-r1-result.json
(4 回同じ。終了コードは毎回 2)
opened: [1, 1, 1, 1] statuses: ['pending', 'pending']

着手前テストが red の場合も code 2 status pending になった。トレーラー欠落の場合は status after trailer drop: dropped だった。

輪番が進まない

群の担当(impl)は群を割り当てた時点で決まり、開き直しても変わらない。上の再現でも 2 つ目の群(担当 codex)は一度も開かれず、結果を残せない同じ担当が起動し続ける

手順書の骨組みが終了コードを捨て、上限も置いていない

SKILL.md の「実行」の骨組み(:315-319)は次の形である。

"$SCRIPTS/launch-cli.sh" "$IMPL" apply "$ID" "$ROUND"
"$LIB/monitor.py" "$ID" --agents "$IMPL" --tmp-dir "$TMP_DIR" \
    --stem-template "{agent}-apply-r$ROUND" --phase apply
# 終了コード 2 = 適用が通らずこの群を取り消した。修正ラウンドは回さない
rf merge-apply "$ID" "$ROUND" || continue

上限の値は骨組みが持たない。工程名(--phase apply)から上限の表を引く。 表は
plugins/ndf/scripts/lib/limits.py だけが持ち、PHASE_TIMEOUT['apply'] は 3600 秒である
limits.py:32-40)。

  • monitor.pyplugins/ndf/scripts/lib/monitor.py)の終了コード(2 = TIMEOUT / 3 = NO_RESULT / 4 = EARLY_ERROR / 5 = STALLED)を見ていない。どの失敗も merge-apply に「結果ファイルなし」として届く
  • merge-apply の終了コード 2 を「群を取り消した」とみなして continue するが、取り込みの前に抜けた場合は群が残っているため、同じ群へ戻る
  • SKILL.md:49(ラウンドの表の「適用ラウンド」の行)と SKILL.md:56 は「適用ラウンドに別の上限を置かない」と定めている。採用件数の上限が群の数を切るという理由だが、同じ群を開き直す場合には効かない。直すときはこの 2 か所を改める必要がある

agy が STALLED になる背景(推定)

  • 無進捗の許容は上限の表が持つ。limits.AGENT_STALLlimits.py:43-48)で agy は 480 秒である
    monitor.py:101DEFAULT_STALL_AGENT_BUILTIN はこの表への別名)
  • cross-refactoring の骨組みは --stall-timeout を渡していない。 limits.py:15
    「cross-refactoring の apply / fix / final-fix には、骨組みが --stall-timeout を引数で
    渡す(テスト 1 回を含む無出力の最長が工程で決まるため)。その許容はこの表に持たない」と書くが、
    SKILL.md:315-319--phase apply だけを渡す。文書とコードが食い違っている
  • 無進捗の判定は err.log / stdout.log / progress.log の合計サイズで行う(monitor.py:792-796)。cross-review は scripts/launch-reviewer.sh:137-142 で agy に $STEM-progress.log へ作業段階を追記させている。cross-refactoring の prompts/*.md には進捗マーカーの指示が無い(grep -n progress prompts/*.md の出力なし、終了コード 1)
  • 適用やテストの実行は出力の無い時間が長いため、agy が動いていても 480 秒で打ち切られている可能性がある。agy を実行して確かめてはいないため、rf646 の agy のログで確認が要る

429 を早期の致命として検知できていない

monitor.pyEARLY_ERROR_FATAL:114-126)は ^HTTP/… 429rate limit exceeded だけを見る。claude が出す "api_error_status":429 を含む行を _scan_early_fatal に渡すと None が返る(一致しない)。そのため 429 は 15 秒で落ちても NO_RESULT として扱われ、上の開き直しに入る。

影響

  • 進行が止まっていることに気づけない(ハートビートは出続ける)
  • 上限のある環境では、再試行がそのまま利用量を消費する(rf646 で 3729 回)
  • 後ろの群が 1 つも適用されない(PR Add: 却下した指摘を per-item で残す(#156 の 1 本目) #535 で 3 件)
  • 収束の判定(新しい提案が出なくなる)と最終ゲートへ到達できず、人が止めるしかない。打ち切ると最終ゲートが未実施で残る
  • 担当の CLI が結果を残さなければ毎回起きる(agy の STALLED・利用上限・認証切れ・容量不足)。既定の参加者に agy が含まれる

回避は手で止め、状態ファイルの群を dropped へ書き換えることだが、手順書には書かれていない。agy の STALLED は、MONITOR_STALL_AGY を大きくすると打ち切りまでの時間を延ばせる。

所要時間への影響(2026-09-01〜09-15 の実測)

集計の方法と全体の値は #662 にある。

提案

関連

出どころ

rf646 の進行ログ(STALLED 4 回 / 429 の 3729 行)。#495 の担当(PR #646)の実施中に発生。PR #535 の構造改善の実測。devbasex/devbase#191(2026-09-17)と PR #757(2026-09-18、v10.15.0 の振り返り issue #550)で同じ事象を確認した。

進行

モード: standard / 作業ツリー: .worktrees/feat/issue-728-apply-intake / 計画: issues/issue-728-647-592-553-plan.md

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

閉じた理由

PR #796 で直り、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