Skip to content

fix(cli-push-runner): push 拒否検知の 40 行 truncate 依存を修正 (push T5) - #282

Merged
aloekun merged 2 commits into
masterfrom
fix/push-refusal-full-output
Jul 17, 2026
Merged

fix(cli-push-runner): push 拒否検知の 40 行 truncate 依存を修正 (push T5)#282
aloekun merged 2 commits into
masterfrom
fix/push-refusal-full-output

Conversation

@aloekun

@aloekun aloekun commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • push 拒否検知 (push_was_refused) の判定対象を 40 行 truncate 済み出力から全量出力に変更し、jj が exit 0 で拒否した際の silent failure を塞ぐ
  • lib-subprocessrun_cmd_shell_unlimited を追加。3 variant の共通骨格 (spawn → drain → wait → combine) は run_cmd_shell_with に集約
  • 表示は成功時のみ 40 行 + 超過明示に cap し、失敗経路は全量表示 (診断情報を落とさない)
  • 未使用になった runner::run_stage_cmd を削除 (capped 経路で control flow 判定する罠の構造的排除)
  • 回帰テスト 10 本を追加し、variant 追加の境界判定を ADR-044 に記録

Context

Why: jj は新規 bookmark の push を拒否するとき、エラー終了せず Refusing to create new remote bookmark を出力して exit 0 を返す。push stage はこの行を出力から検知していたが、判定対象が run_stage_cmd (= run_cmd_shell_capped、MAX_LINES=40 の silent truncate) の出力だったため、jj の出力が 40 行を超えて拒否行が cap の外へ落ちると拒否を見逃し、リモート未反映のまま exit 0 で完了していた。後続の cli-pr-monitor は旧 head を監視し始める。

Trigger: 2026-07-16 の push パイプライン調査 (T5)。lib-subprocess の doc は当初から「control flow 判定に出力を使う callsite で capped variant を使うな」と明記しており、その契約違反として特定された。正しい variant (unlimited) が run_cmd_shell family に存在しなかったため、callsite は間違った variant を選ぶしかなかった — この観察を ADR-044 に記録している。

Scope decision:

  • contains("refusing to") の厳格化は見送り。誤検知 (push 成功を失敗と報告) は出力表示で気付いて再実行できるが、検知漏れは本 PR が直す事故そのもの。リスクが非対称なため ADR-043 (fail-closed) に従い部分一致を維持する
  • cli-pr-monitorpush_to_remote にも拒否検知が無い同型の穴を発見したが、1 PR 1 変更のため本 PR では触れず計画の backlog に追加した

Validation

  • cargo test -p cli-push-runner: 206 pass (T5 回帰 6 本を含む。run_stage_cmd の 2 本を削除したため 208 → 206)
  • cargo test -p lib-subprocess: 31 pass (unlimited variant 4 本を含む)
  • cargo clippy --workspace --all-targets --all-features -- -D warnings: warning 0
  • 回帰テストの有効性: run_push_cmd を capped 版に戻すと 3 本が fail することを確認 (テストが素通りしない実証)
  • サンドボックス実機 before/after: 拒否行を 41 行目に置いた fake push command で、before (現行配布 exe) = [push] 成功 + exit 0 (silent failure を再現) / after = 拒否検知 + exit 3。成功経路 (50 行・拒否なし) は 40 行 + ... (10 lines truncated) 表示 + exit 0 を維持
  • pnpm push pre-push review: verdict=APPROVE (simplicity / security とも。1 iteration / takt 313s / 合計 366s)

References

Summary by CodeRabbit

  • 改善

    • リモートへの push 拒否を、出力が長い場合でも正しく検知できるようになりました。
    • 拒否や失敗時の診断情報を失わず、原因を確認しやすくなりました。
    • ログ表示は見やすく制限しつつ、判定には完全なコマンド出力を使用します。
  • ドキュメント

    • push 処理の検証結果、運用手順、今後の対応項目を更新しました。
    • コマンド実行方式の判断基準と追加された実行バリエーションを記録しました。
  • テスト

    • 長い出力による拒否検知漏れや、ログ表示制限に関する回帰テストを追加しました。

jj は新規 bookmark の push を拒否するとき、エラー終了せず "Refusing to
create new remote bookmark" を出力して exit 0 を返す。push stage はこれを
出力の部分一致で検知していたが、判定対象が run_stage_cmd
(= run_cmd_shell_capped、MAX_LINES=40 の silent truncate) の出力だったため、
jj の出力が 40 行を超えて拒否行が cap の外へ落ちると拒否を見逃し、
リモート未反映のまま exit 0 で完了する。後続の cli-pr-monitor は旧 head を
監視し始める。

lib-subprocess の doc は「control flow 判定に出力を使う callsite で capped
variant を使うな」と当初から明記していたが、正しい variant (unlimited) が
run_cmd_shell family に存在せず、callsite は間違った variant を選んでいた。

変更:
- lib-subprocess: run_cmd_shell_unlimited を追加。drain_pipe_unlimited は
  pipe 単体、_capped_reporting は cap が残るため、どちらも判定用には不足。
  3 つ目の copy になる骨格 (spawn → drain → wait → combine) は
  run_cmd_shell_with に集約し、各 variant は drain 戦略の違いだけを表す。
  境界判定は ADR-044 § 後続の variant 追加 に記録。
- push stage: run_push_cmd (unlimited) で全量取得し、push_was_refused は
  全量出力に対して判定する。表示は成功時のみ cap_for_log (先頭 40 行 +
  超過明示) を通し、失敗経路は全量表示 (診断情報を落とさない)。成功時の
  ログ量は従来どおり。
- runner::run_stage_cmd を削除。push stage が唯一の呼び出し元だったため
  未使用になった。dead code 除去に加え、capped 経路で control flow 判定を
  する罠の構造的排除。MAX_LINES は表示用として残置し doc に判定禁止を明記。

contains(\"refusing to\") の厳格化は見送り (ユーザー承認済み)。誤検知は出力
表示で気付いて再実行できるが、検知漏れは本 PR が直す事故そのものでリスクが
非対称なため、ADR-043 (fail-closed) に従い部分一致を維持する。

検証:
- 回帰テスト mod t5_truncated_refusal_detection 6 本 + lib-subprocess 4 本
  (ADR-049 の流儀。bad = 41 行目の拒否行を検知 / good = 40 行超の正常出力を
  誤検知しない / 表示 cap は判定に影響しない)。run_push_cmd を capped に
  戻すと 3 本が fail することを確認済み (回帰テストが素通りしない実証)。
- サンドボックスの jj repo で配布 exe と修正後 exe を比較。拒否行を 41 行目に
  置いた fake push command で before = [push] 成功 + exit 0 (silent failure
  再現) / after = 拒否検知 + exit 3。成功経路 (50 行) は 40 行 +
  "... (10 lines truncated)" 表示で exit 0 を維持。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 13bd50c3-1220-4662-bfb6-56d9dcaca174

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

run_cmd_shell_unlimited と共通実行ヘルパーを追加し、push 拒否判定を全量出力で行うよう変更しました。ログ表示のみ行数制限を適用し、回帰テスト、ADR、修正計画の記録も更新しています。

Changes

Push output handling

Layer / File(s) Summary
共有 subprocess variant と実行骨格
src/lib-subprocess/src/lib.rs
3つの run_cmd_shell_* variant が run_cmd_shell_with に委譲し、drain 戦略だけを切り替える構成になりました。run_cmd_shell_unlimited の終了状態、全量保持、timeout をテストしています。
push stage の全量判定と表示 cap
src/cli-push-runner/src/stages/push.rs, src/cli-push-runner/src/runner.rs
push 拒否判定には全量出力を渡し、ログ表示だけ cap_for_log で切り詰めます。run_stage_cmd を削除し、継承入出力を使う run_cmd_inherit を追加しました。
ADR・修正計画の記録更新
docs/adr/adr-044-subprocess-utility-extraction-boundary.md, docs/push-pipeline-fix-plan.md
unlimited variant、共通骨格、回帰検証、dogfood 実測、T13 backlog の判断を記録しました。

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant run_push
  participant run_push_cmd
  participant run_cmd_shell_unlimited
  participant push_was_refused
  participant cap_for_log
  run_push->>run_push_cmd: pushを実行して全量出力を取得
  run_push_cmd->>run_cmd_shell_unlimited: unlimited drainでコマンド実行
  run_cmd_shell_unlimited-->>run_push_cmd: 全量出力を返す
  run_push->>push_was_refused: 全量出力で拒否判定
  run_push->>cap_for_log: 表示用出力をcap
  cap_for_log-->>run_push: 表示用出力を返す
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed push 拒否検知が 40 行 truncate に依存していた問題の修正を具体的に示しており、変更内容と一致しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/push-refusal-full-output

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: issue_comment (created) / 実行 run
  • CI: CodeRabbit のみが check として登録されており pending (Review in progress)。他の CI check は現時点で存在しない。mergeable: MERGEABLE / mergeStateStatus: UNSTABLE (pending check起因)
  • レビュー状況: 人間レビューなし (reviews API 0件)。CodeRabbitはレビュー未着 (issue コメントに "Currently processing new changes... please wait" のプレースホルダーのみ、実際の finding コメントはまだ0件)。インライン指摘も0件
  • Verdict: approved (findings が0件のため analyze-coderabbit.md の規定 "If the findings array is empty, report 'No actionable findings' with verdict approved" に従う。ただし CodeRabbit レビューは未完了であり、後続コメントで新規指摘が来れば再評価が必要)

Applicable Findings (Critical / High / Major)

該当なし

Applicable Findings (Medium 以下)

該当なし

Filtered (not applicable)

該当なし

Diff 概要 (軽量サマリー)

  • 変更ファイル: 5件 (docs 2 / src 3)
    • docs/adr/adr-044-subprocess-utility-extraction-boundary.md: variant 追加時の境界判定事例を追記 (既存 ADR の適用実例の記録、決定内容の変更なし)
    • docs/push-pipeline-fix-plan.md: T5 実施結果・PR番号の修正 (chore(push-runner-config): refute facet の dogfood を開始 (push T4) #281 参照の訂正)・backlog 更新
    • src/lib-subprocess/src/lib.rs: run_cmd_shell_unlimited variant を追加。3 variant (_capped / _capped_reporting / _unlimited) の共通骨格を run_cmd_shell_with に集約するリファクタ。テスト4本追加
    • src/cli-push-runner/src/stages/push.rs: push 拒否判定 (push_was_refused) を全量出力に対して行うよう変更 (run_push_cmdrun_cmd_shell_unlimited を使用)、表示側は cap_for_log で40行に制限。テスト6本追加 (t5_truncated_refusal_detection)
    • src/cli-push-runner/src/runner.rs: 未使用になった run_stage_cmd を削除 (dead code除去)、MAX_LINES は表示用として残置しdocを追記
  • 変更の性質: バグ修正 (push 拒否検知が40行 truncate の外に落ちた拒否メッセージを見逃し silent failure で exit 0 になる不具合)。回帰テスト・サンドボックス実機での before/after 検証がPR body/docに記録済み

次のアクション

  • CodeRabbit のレビューが完了し新規指摘が投稿された時点で、本 workflow の次回起動により再分析されるのを待つ (現時点では待機・ポーリング不要)
  • 現状 blocking な CI check は無く、CodeRabbit pending 解消後に green になる見込み。人間によるアクションは特に不要

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib-subprocess/src/lib.rs`:
- Line 287: wait_with_timeout_basic を使用する旨の説明を、Err 経路で child が kill/reap される実際の
semantics に合わせて修正してください。特に kill_and_join_err による処理を反映し、Err 経路では child を kill
しないという誤った記述を削除してください。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a89fd615-1928-4304-a088-fdd40f57794d

📥 Commits

Reviewing files that changed from the base of the PR and between fbc470c and 1ef60ba.

📒 Files selected for processing (5)
  • docs/adr/adr-044-subprocess-utility-extraction-boundary.md
  • docs/push-pipeline-fix-plan.md
  • src/cli-push-runner/src/runner.rs
  • src/cli-push-runner/src/stages/push.rs
  • src/lib-subprocess/src/lib.rs

Comment thread src/lib-subprocess/src/lib.rs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: pull_request_review (submitted) / 実行 run
  • CI: analyze (本 workflow 自身) が pendingCodeRabbitpass (Review completed)。他に failing check なし。mergeable: MERGEABLE / mergeStateStatus: UNSTABLE (pending check起因)
  • レビュー状況: CodeRabbit がレビューを提出済み (state: COMMENTED, actionable comments: 1件、Minor)。人間レビューなし (reviews API 中、人間レビューは0件、reviewDecision は空)
  • Verdict: user_decision (適用可能な指摘は Minor 1件のみ。analyze-coderabbit.md の 3-way 基準で Medium 以下のみの場合は user_decision)

Applicable Findings (Critical / High / Major)

該当なし

Applicable Findings (Medium 以下)

# File (Line) Reviewer Issue Recommended Action
1 src/lib-subprocess/src/lib.rs:287 CodeRabbit run_cmd_shell_capped の docstring が「Err 経路で child を kill しない」と記載しているが、実際は同ファイル内 run_cmd_shell_with の Err 経路で kill_and_join_err が呼ばれ child を kill/reap する (256行目付近で確認済み、実装と逆の記述)。severity: Minor (CodeRabbit 申告値) docstring を実際の semantics に合わせて修正 (CodeRabbit 提案 diff: 「内部で wait_with_timeout_basic を使用するが、Err 経路では共通 helper が child を kill/reap する。」への差し替え)

Filtered (not applicable)

該当なし

次のアクション

  • Minor なドキュメント記述の誤りのため、次回のローカルセッションで docstring 修正 (1行差し替え) を適用することを推奨
  • ブロッキングな CI failure は無し。analyze check の pending は本 workflow 自身の実行中ステータスであり、完了後に解消される見込み

…h T5)

CodeRabbit Minor (src/lib-subprocess/src/lib.rs):
run_cmd_shell_capped の doc が「Err 経路で child を kill しない basic
semantics」と書いていたが、実際は Err 経路を kill_and_join_err が受けて
child を kill + reap し reader thread も join する。wait_with_timeout_basic
単体の性質としては正しい記述が、kill_and_join_err 導入 (PR #208) 以降 stale に
なっていた pre-existing の不整合。child lifecycle は 3 variant 共通なので、
記述を共通骨格 run_cmd_shell_with の doc に集約し、variant 側は参照のみにした。

pre-push simplicity 警告 (非ブロッキング、採用):
- cap_for_log の "... (N lines truncated)" 書式が drain_pipe_capped_reporting
  と重複していた。切り詰めの実装自体は共有できない (pipe を streaming しながら
  数える版 vs materialize 済み文字列を切る版) が、書式片は共有できるという指摘は
  妥当なので lib_subprocess::truncation_notice として切り出し、両者から使う形に
  した。単数/複数形の unit test 2 本を追加。
- 計画書 T5 行の「実装済 (本 PR)」を「実装済 (PR #282)」に backfill。T4 行が
  「本 PR」のまま放置され PR #282 で backfill する羽目になった負債を、同じ形で
  繰り返さないため。

検証: cargo test -p cli-push-runner 206 pass / -p lib-subprocess 33 pass、
cargo clippy --workspace --all-targets --all-features -- -D warnings で warning 0。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: pull_request_review (submitted) / 実行 run
  • CI: analyze (本 workflow 自身) が pendingCodeRabbitpass (Review skipped: incremental reviews are disabled — 新規コミットが無いレビューコメントのみの往復のため)。他に failing check なし。mergeable: MERGEABLE / mergeStateStatus: UNSTABLE (pending check起因、本 workflow 完了で解消見込み)
  • レビュー状況:
    • CodeRabbit: 03:30 に actionable finding 1件 (Minor, src/lib-subprocess/src/lib.rs:287 の docstring 記述誤り) を提出 → 03:51 に PR 作者 (aloekun) がコミット 22e5fc66 で修正した旨を返信 → 03:52 に CodeRabbit が修正内容を確認し ✅ Confirmed as addressed / <!-- <review_comment_addressed> --> で解消済みとマーク
    • 人間レビュー: aloekun (PR 作者本人) が上記インライン返信のみ、formal approve/request-changes なし。reviewDecision は空
  • Verdict: approved

Applicable Findings (Critical / High / Major)

該当なし

Applicable Findings (Medium 以下)

該当なし (唯一の指摘は解決済み — 下記参照)

Filtered (not applicable)

該当なし

解決済み指摘 (参考)

# File (Line) Reviewer Issue 状態
1 src/lib-subprocess/src/lib.rs:287 CodeRabbit run_cmd_shell_capped の docstring が「Err 経路で child を kill しない」と記載 (実際は run_cmd_shell_withkill_and_join_err が kill/reap する、逆の記述) commit 22e5fc66 で修正 (child lifecycle 記述を共通骨格 run_cmd_shell_with の doc に集約し stale 再発を構造的に防止)。CodeRabbit が修正内容を確認済み

次のアクション

  • 追加対応不要。唯一の Minor 指摘は PR 作者により修正済みで CodeRabbit も確認済み。analyze check の pending 解消を待って通常のマージ判断に進んで差し支えない

@aloekun
aloekun merged commit a519232 into master Jul 17, 2026
2 of 3 checks passed
@aloekun
aloekun deleted the fix/push-refusal-full-output branch July 17, 2026 04:05
aloekun added a commit that referenced this pull request Jul 17, 2026
* docs(push-pipeline-fix-plan): T6 の PR 番号を #283 に backfill

T6 の作業コミット時点では PR が未採番だったため、計画書の §4 実施結果と §8
判定記録に「PR 未採番 — 採番後に backfill」と書いて負債を明示していた。PR #283
がマージされたため採番情報のみを更新する。

変更 (3 箇所、いずれも採番情報のみ):
- §4 T6 実施結果の見出し: PR 未採番 → PR #283
- §4 T6 の backlog 10 への申し送り: 「本 PR では触れず」→「PR #283 では触れず」
  (T5 が §4/§8 の「本 PR」を番号へ解決した慣習に揃える)
- §8 判定記録の T6 行: PR 未採番 → PR #283

由来: PR #282 (T5) の post-PR レビューで「T4 行が『本 PR』のまま放置され PR #282
で backfill する羽目になった」負債が指摘され、同じ形を繰り返さないために T6 では
未採番であることを明示していた。本コミットでその明示を回収する。

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(hooks-stop-quality): 品質ゲートの cwd 依存を修正 (push パイプライン改善 T7)

Stop hook はセッションの cwd を継承して起動されるため、cwd がリポジトリルート
以外 (例: .takt/runs に cd したまま Stop) だと 2 つの症状が黙って出ていた:

1. hooks-config.toml のルート相対 step (file-length) が「指定されたパスが
   見つかりません」で失敗し、品質ゲートが誤 block する (2026-07-16 に実発火)。
   pnpm / cargo 系 step は設定ファイルを上方探索するため偶然通っており、
   ルート相対パスを書いた step だけが壊れる非対称が発見を遅らせていた。
2. takt subsession 判定が <cwd>/.takt/runs を探して空振りし、active run を
   検出できない。ADR-004 の edit:false subsession skip が効かなくなる。

同一の根本原因なので main 冒頭で cwd を 1 度だけプロジェクトルートへ正規化する。
ルートは exe パス (<root>/.claude/<hook>.exe) から導出する — CLAUDE_PROJECT_DIR
env は VSCode 拡張環境で空になることを実測確認した (ADR-005 の不安定性が現存)。
config / pipeline lock / telemetry が既に採る exe-relative 規約と同形。

ルート特定不能時は警告のみで継続 (fail-open、pipeline_is_running と同じ線引き)。
main.rs が 800 行上限に触れたため takt 判定を takt_subsession.rs へ分離した。

回帰テスト: tests/t7_cwd_independence.rs に E2E 5 本 + unit 2 本 (26 → 33)。
exe を <root>/.claude/ に staging して spawn し、exe-relative 解決を実配置で
検証する。正規化の呼び出しを外すと bad 2 本が失敗し good 3 本は通ることを確認済み。

* fix(review): apply CodeRabbit fixes for #284

Resolved findings:
- [Major] src/hooks-stop-quality/tests/t7_cwd_independence.rs:96 hook の終了ステータスを確認してください

* fix(review): CodeRabbit 指摘の stderr 出力を補完 (#284)

auto-fix が追加した assert_hook_success は exit code assert 自体は入れたが、
指摘の「失敗時は stderr を出す」部分が未達だった: メッセージに stdout を渡しており、
かつ stderr.join() より前に呼ばれるため構造上 stderr を出せない。

本 hook の診断 (cwd 正規化の警告等) は eprintln! = stderr にしか出ないため、
指摘が想定する「非 0 exit かつ stdout が空」の失敗では stderr だけが手掛かりになる。
stderr を join してから assert に渡す形へ補正した。

guard が空振りでないことを実証済み: staged exe を where.exe (非 0 exit・stdout 空) に
差し替えると 5 本すべてが exit code Some(2) で失敗する (guard 導入前なら None を
期待する 3 本が false green で素通りしていた)。

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant