Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
- [ADR-013: Merge Pipeline — PR マージ + ローカル同期](docs/adr/adr-013-merge-pipeline.md)
- [ADR-014: Post-Merge Feedback — マージ後のフィードバックループによる再発防止](docs/adr/adr-014-post-merge-feedback.md) *(試験運用)*
- [ADR-015: Push Pipeline を takt ベースの push-runner に移行](docs/adr/adr-015-push-runner-takt-migration.md) *(Supersedes ADR-008 の push 前パイプライン部分)*
- [ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略](docs/adr/adr-016-long-running-command-strategy.md)
- [ADR-017: takt バージョン固定と検証環境の維持](docs/adr/adr-017-takt-version-pinning.md)

## Build

Expand Down
65 changes: 65 additions & 0 deletions docs/adr/adr-016-long-running-command-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略

## ステータス

承認済み (2026-04-14)

## コンテキスト

ADR-015 で takt ベースの push-runner を導入した際、Claude Code の Bash ツールでの実行中にパイプラインが痕跡なく消える問題が発生した。

### 問題

1. Bash ツールのデフォルトタイムアウトは 120 秒
2. takt AI レビューは 2〜10 分かかる
3. タイムアウト時、Bash ツールはプロセスツリーごと kill する
4. kill されたプロセスはログにも state file にも痕跡を残さない
5. 調査者からは「パイプラインが実行中のまま進捗がない」ように見える

### 影響を受けるコマンド

| コマンド | 典型的な実行時間 | デフォルトで安全か |
|---------|----------------|-----------------|
| `pnpm push` (takt レビューあり) | 2〜10 分 | 不安全 |
| `pnpm push` (takt レビューなし/clean path) | 2〜3 分 | 不安全 |
| `pnpm create-pr` + monitor daemon 起動 | 10〜30 秒 | 安全 |
| `pnpm merge-pr` | 10〜30 秒 | 安全 |
| `cargo build --release` (単体) | 5〜15 秒 | 安全 |
| `pnpm build:all` (全 exe 一括) | 1〜2 分 | 境界的 |

## 決定

### 長時間コマンドは `timeout: 600000` + `run_in_background: true` で実行する

Claude Code の Bash ツールには以下の制御パラメータがある:

- `timeout`: 最大 600000ms (10 分)。デフォルト 120000ms (2 分)
- `run_in_background`: true にするとバックグラウンド実行。完了時に通知を受け取れる

`pnpm push` のように takt AI レビューを含むコマンドは、必ず以下の設定で実行する:

```
timeout: 600000
run_in_background: true
```
Comment on lines +41 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

コードフェンスに言語指定がありません(MD040)。

markdownlint 警告どおり、フェンスに言語を付けてください。

🛠 修正案
-```
+```yaml
 timeout: 600000
 run_in_background: true
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 41-41: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/adr/adr-016-long-running-command-strategy.md` around lines 41 - 44, The
fenced code block containing the lines "timeout: 600000" and "run_in_background:
true" is missing a language tag (MD040); update that fenced block in
adr-016-long-running-command-strategy.md to include a language identifier such
as "yaml" (e.g., change ``` to ```yaml) so markdownlint no longer reports the
issue and the snippet is syntax-highlighted correctly.


### バックグラウンド実行の利点

1. タイムアウトが 10 分に延長される
2. 実行中に他の作業が可能
3. 完了通知で結果を確認できる
4. レートリミット消費を抑制できる (ポーリング不要)

### パイプライン変更時のブートストラップ問題

push パイプライン自体を変更する場合、中間コミットではパイプラインが壊れる可能性がある。

- コミット 1 (Rust crate のみ) を push → 古いパイプラインが動き takt 未導入で失敗
- コミット 3 (統合) まで揃って初めて新パイプラインが動く

**対策**: パイプライン変更コミットは squash して 1 回でアトミックに push する。

## 影響

- `pnpm push` を Claude Code から実行する際の標準手順が確立される
- hooks や skills の中で長時間コマンドを呼ぶ際の指針になる
52 changes: 52 additions & 0 deletions docs/adr/adr-017-takt-version-pinning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ADR-017: takt バージョン固定と検証環境の維持

## ステータス

承認済み (2026-04-14)

## コンテキスト

PR #33 で takt 0.35.4 (Agent SDK 0.2.105) を導入した際、Windows 環境で Claude CLI の呼び出しが失敗した。

### 障害の詳細

- **エラー**: `Claude CLI failed (1): コマンドまたはファイル名が正しくありません` (Shift-JIS)
- **原因**: takt 0.35.4 にバンドルされる Agent SDK 0.2.105 が、Windows での Claude CLI spawn に失敗
- **影響**: takt の Phase 1 (execute) が 0.25 秒で異常終了し、AI レビューが一切実行されない
- **解決**: takt 0.35.3 (Agent SDK 0.2.98) へのダウングレードで正常動作を確認

### 根本原因の推定

Agent SDK の Claude Code 呼び出し方式が 0.2.98 → 0.2.105 の間で変更され、Windows のプロセス spawn 互換性が破壊された。takt 自体のコード変更ではなく、依存ライブラリの破壊的変更。

## 決定

### 1. キャレットなしでバージョンを固定する

```json
{
"devDependencies": {
"takt": "0.35.3"
}
}
```

`^0.35.3` ではなく `0.35.3` とすることで、`pnpm update` による意図しないアップグレードを防止する。

### 2. takt-test-vc を検証環境 (staging) として位置づける

バージョンアップ手順:

1. `E:\work\takt-test-vc` で新バージョンの takt をインストール
2. `pnpm push:runner` でフルパイプライン実行を検証
3. Windows 環境での Claude CLI 呼び出しが正常であることを確認
4. 確認後、このプロジェクトの package.json を更新

### 3. Agent SDK のバージョンも間接的に管理される

takt は `@anthropic-ai/claude-agent-sdk` を `^0.2.71` で依存している。takt のバージョンを固定しても、Agent SDK は semver 範囲内で更新される可能性がある。pnpm-lock.yaml が実質的なロックとなる。

## 影響

- takt のバージョンアップには必ず takt-test-vc での事前検証が必要
- 派生プロジェクトへの deploy 時も同じバージョンの takt を使用すること
13 changes: 13 additions & 0 deletions docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@

- [x] **CronCreate がサブセッションに閉じ込められる**: ~~`pnpm push` 実行時、`review:ai` (`claude -p "/pre-push-review"`) のサブセッションが「最新セッション」となり、後続の `cli-pr-monitor --monitor-only` の `--continue` がサブセッションに接続してしまう。~~ ADR-015 で push-runner が takt ベースに移行されたため、`claude -p` 経由のサブセッション問題は解消。takt がプロセス内で AI レビューを管理するため、CronCreate のセッション分離問題は発生しない。

## PR #33 後の改善タスク (優先度順)

- [ ] **cli-pr-monitor: jj 環境での PR 作成時 --head 自動補完**: `run_create_pr()` で `--head` 未指定時に `get_jj_bookmarks()` で jj bookmark を自動検出し補完する。monitor-only モードには同等のフォールバック実装済み。毎回の PR 作成で手動回避が必要な状態のため優先度高
- [ ] **ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略**: デフォルト 120s タイムアウトでプロセスが kill される問題。`timeout: 600000` + `run_in_background: true` を長時間コマンドに必須とする方針を ADR として記録
- [ ] **ADR-017: takt バージョン固定と検証環境の維持**: takt 0.35.4 で Windows 環境が壊れた実績。キャレットなし固定 + takt-test-vc を検証環境として位置づける方針を ADR として記録
Comment on lines +16 to +18

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

このPRで完了した項目のチェック状態が未更新です。

--head 自動補完実装、および ADR-016/017 追加が入っているため、進捗管理の正確性のために [x] へ更新した方がよいです。

📝 更新案
-- [ ] **cli-pr-monitor: jj 環境での PR 作成時 --head 自動補完**: `run_create_pr()` で `--head` 未指定時に `get_jj_bookmarks()` で jj bookmark を自動検出し補完する。monitor-only モードには同等のフォールバック実装済み。毎回の PR 作成で手動回避が必要な状態のため優先度高
-- [ ] **ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略**: デフォルト 120s タイムアウトでプロセスが kill される問題。`timeout: 600000` + `run_in_background: true` を長時間コマンドに必須とする方針を ADR として記録
-- [ ] **ADR-017: takt バージョン固定と検証環境の維持**: takt 0.35.4 で Windows 環境が壊れた実績。キャレットなし固定 + takt-test-vc を検証環境として位置づける方針を ADR として記録
+- [x] **cli-pr-monitor: jj 環境での PR 作成時 --head 自動補完**: `run_create_pr()` で `--head` 未指定時に `get_jj_bookmarks()` で jj bookmark を自動検出し補完する。monitor-only モードには同等のフォールバック実装済み。毎回の PR 作成で手動回避が必要な状態のため優先度高
+- [x] **ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略**: デフォルト 120s タイムアウトでプロセスが kill される問題。`timeout: 600000` + `run_in_background: true` を長時間コマンドに必須とする方針を ADR として記録
+- [x] **ADR-017: takt バージョン固定と検証環境の維持**: takt 0.35.4 で Windows 環境が壊れた実績。キャレットなし固定 + takt-test-vc を検証環境として位置づける方針を ADR として記録
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- [ ] **cli-pr-monitor: jj 環境での PR 作成時 --head 自動補完**: `run_create_pr()` で `--head` 未指定時に `get_jj_bookmarks()` で jj bookmark を自動検出し補完する。monitor-only モードには同等のフォールバック実装済み。毎回の PR 作成で手動回避が必要な状態のため優先度高
- [ ] **ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略**: デフォルト 120s タイムアウトでプロセスが kill される問題。`timeout: 600000` + `run_in_background: true` を長時間コマンドに必須とする方針を ADR として記録
- [ ] **ADR-017: takt バージョン固定と検証環境の維持**: takt 0.35.4 で Windows 環境が壊れた実績。キャレットなし固定 + takt-test-vc を検証環境として位置づける方針を ADR として記録
- [x] **cli-pr-monitor: jj 環境での PR 作成時 --head 自動補完**: `run_create_pr()` で `--head` 未指定時に `get_jj_bookmarks()` で jj bookmark を自動検出し補完する。monitor-only モードには同等のフォールバック実装済み。毎回の PR 作成で手動回避が必要な状態のため優先度高
- [x] **ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略**: デフォルト 120s タイムアウトでプロセスが kill される問題。`timeout: 600000` + `run_in_background: true` を長時間コマンドに必須とする方針を ADR として記録
- [x] **ADR-017: takt バージョン固定と検証環境の維持**: takt 0.35.4 で Windows 環境が壊れた実績。キャレットなし固定 + takt-test-vc を検証環境として位置づける方針を ADR として記録
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/todo.md` around lines 16 - 18,
ドキュメントの進捗チェックが未更新になっているので、該当チェックリスト項目を完了済みに変更してください:`cli-pr-monitor: jj 環境での PR
作成時 --head 自動補完`、`ADR-016: Claude Code Bash ツールでの長時間コマンド実行戦略`、`ADR-017: takt
バージョン固定と検証環境の維持` の各行先頭の `[ ]` を `[x]` に置き換え、変更後の todo.md をコミットして PR
に反映してください(該当テキストはファイル内の該当見出し文言で検索して特定できます)。

- [ ] **post-pr-create-review-check スキル: exe 名更新**: アーキテクチャ図の `hooks-post-pr-monitor.exe` を `cli-pr-monitor.exe` に修正 (ADR-012 命名規約反映漏れ)
- [x] **templates/ に push-runner-config.toml 追加**: 派生プロジェクトへの deploy:hooks で push-runner-config.toml が配布されない問題。テンプレート追加 + deploy-hooks.ts 更新
- [ ] **pre-push-review スキルの役割整理**: takt 導入済みプロジェクトでは不要に。takt 未導入の派生プロジェクト向けにフォールバックとして維持するか、廃止するか検討

## 次ステップ: cli-pr-monitor の takt 化

- [ ] **cli-pr-monitor を takt ベースに段階的移行**: daemon ポーリング完了後に takt ワークフローで CodeRabbit 指摘の自動分析を実行。Phase 2 では fix loop による自動修正 + re-push まで一気通貫で処理する構想。push-runner と同様に「機械的ポーリングは Rust、AI 分析は takt」の分離原則を適用する (ADR-015 次ステップ参照)

## プロセス改善

- [ ] **マージ後フィードバックの定常化**: PR マージ後に毎回、セッションで得られた知見を整理し「ADR として記録すべきもの」「既存の仕組みに反映すべきもの」をフィードバックとして提示する。post-merge-feedback スキルの拡張、または独立したチェックリストとして運用化を検討
27 changes: 18 additions & 9 deletions scripts/deploy-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ function copyFile(src: string, dest: string): void {
logger.info(` copied: ${basename(src)}`);
}

function notifyIfMissing(p: string, missing: string, hint: string): void {
if (!existsSync(p)) {
logger.info(` NOTE: ${missing}`);
logger.info(` ${hint}`);
}
}

function deployTo(targetDir: string): boolean {
const targetClaude = join(targetDir, ".claude");

Expand All @@ -89,15 +96,17 @@ function deployTo(targetDir: string): boolean {
copyFile(src, join(targetClaude, exe));
}

const configDest = join(targetClaude, "hooks-config.toml");
if (!existsSync(configDest)) {
logger.info(
" NOTE: hooks-config.toml not found — please create one for this project"
);
logger.info(
" See templates/ directory for language-specific examples"
);
}
notifyIfMissing(
join(targetClaude, "hooks-config.toml"),
"hooks-config.toml not found — please create one for this project",
"See templates/ directory for language-specific examples"
);

notifyIfMissing(
join(targetDir, "push-runner-config.toml"),
"push-runner-config.toml not found — takt push-runner requires this at repo root",
"See templates/push-runner-config.toml for a starting point"
);

const templateSrc = join(CLAUDE_DIR, SETTINGS_TEMPLATE);
if (existsSync(templateSrc)) {
Expand Down
27 changes: 22 additions & 5 deletions src/cli-pr-monitor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,16 +701,33 @@ fn run_create_pr(gh_args: &[String]) -> i32 {
log_info("PR 作成モード");

// --body に改行が含まれる場合、--body-file に自動変換
let (final_args, _body_tempfile) = convert_body_to_file(gh_args);
let (mut final_args, _body_tempfile) = convert_body_to_file(gh_args);

// jj 環境対応: --head 未指定時に jj bookmark から自動補完
// gh pr create は git の current branch を検出するが、jj 環境では
// "not on any branch" エラーになるため、明示的に --head を指定する
if !final_args.iter().any(|a| a == "--head") {
let bookmarks = get_jj_bookmarks();
if let Some(bookmark) = bookmarks.first() {
log_info(&format!(
"jj bookmark '{}' を --head に自動補完",
bookmark
));
final_args.push("--head".to_string());
final_args.push(bookmark.clone());
}
}
Comment on lines +709 to +719

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

GitHub CLI の gh pr create で、--head=形式は正式サポートされていますか?また--head の重複指定時の優先ルール(先勝ち/後勝ち/エラー)を確認してください。

💡 Result:

GitHub CLIのgh pr createコマンドで--head=形式は正式にサポートされています。公式マニュアルで-H, --head として記載されており、デフォルトは現在のブランチです。形式はowner:branchもサポートされ、フォークからのPR作成に使用されます(例: --head monalisa:feature)。 --headの重複指定時の優先ルールについては、ソースコードおよびドキュメントで明示的に記載されていません。GitHub CLIはCobraライブラリを使用しており、一般的にstring型のフラグは最後に指定された値(後勝ち)が優先されます。重複指定でエラーが発生するという情報も見つかりませんでした。

Citations:


--head=<value> 形式の指定を見落として自動補完が走る問題があります。

コードが "--head" の完全一致のみをチェックしているため、ユーザーが --head=feature-x のような形式でオプションを指定した場合、自動補完ロジックがこれを検出できず、ジジ倉庫のブックマークを追加してしまいます。GitHub CLI では最後に指定された値が優先されるため、ユーザーの意図が無視されて、意図しないブランチが PR のヘッドになります。

修正例
-    if !final_args.iter().any(|a| a == "--head") {
+    let has_head = final_args
+        .iter()
+        .any(|a| a == "--head" || a.starts_with("--head="));
+    if !has_head {
         let bookmarks = get_jj_bookmarks();
         if let Some(bookmark) = bookmarks.first() {
             log_info(&format!(
                 "jj bookmark '{}' を --head に自動補完",
                 bookmark
             ));
             final_args.push("--head".to_string());
             final_args.push(bookmark.clone());
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !final_args.iter().any(|a| a == "--head") {
let bookmarks = get_jj_bookmarks();
if let Some(bookmark) = bookmarks.first() {
log_info(&format!(
"jj bookmark '{}' を --head に自動補完",
bookmark
));
final_args.push("--head".to_string());
final_args.push(bookmark.clone());
}
}
let has_head = final_args
.iter()
.any(|a| a == "--head" || a.starts_with("--head="));
if !has_head {
let bookmarks = get_jj_bookmarks();
if let Some(bookmark) = bookmarks.first() {
log_info(&format!(
"jj bookmark '{}' を --head に自動補完",
bookmark
));
final_args.push("--head".to_string());
final_args.push(bookmark.clone());
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli-pr-monitor/src/main.rs` around lines 709 - 719, The current check
only looks for exact "--head" in final_args and misses "--head=<value>" forms;
update the presence check in the block that calls get_jj_bookmarks()/log_info()
so it treats any argument that equals "--head" or starts_with("--head=") as
present (e.g., change the any(|a| a == "--head") predicate to any(|a| a ==
"--head" || a.starts_with("--head="))). This ensures you only auto-complete with
get_jj_bookmarks() when neither "--head" nor "--head=<value>" was provided; keep
the rest of the logic that pushes "--head" and the bookmark as-is.


log_info(&format!(
"実行: gh pr create {}",
final_args
.iter()
.map(|a| if a.contains(' ') {
format!("\"{}\"", a)
} else {
a.clone()
.map(|a| {
if a.contains(' ') {
format!("\"{}\"", a)
} else {
a.clone()
}
})
.collect::<Vec<_>>()
.join(" ")
Expand Down
40 changes: 40 additions & 0 deletions templates/push-runner-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# push-runner-config.toml -- cli-push-runner (takt ベース) の設定テンプレート
#
# pnpm push で起動される push-runner.exe がこのファイルを読み込む。
# リポジトリルートに push-runner-config.toml として配置する。
#
# プロジェクトに合わせて quality_gate.groups の commands を変更すること。

[quality_gate]
parallel = true
step_timeout = 120

# プロジェクトの品質チェックコマンドに置き換えてください
[[quality_gate.groups]]
name = "lint"
commands = ["pnpm lint"]

[[quality_gate.groups]]
name = "test"
commands = ["pnpm test"]

[[quality_gate.groups]]
name = "build"
commands = ["pnpm build"]

[diff]
# jj 環境: "jj diff -r @"
# git 環境: "git diff origin/HEAD...HEAD"
command = "jj diff -r @"
output_path = ".takt/review-diff.txt"

[takt]
workflow = "pre-push-review"
task = "pre-push review"
extra_args = ["--pipeline", "--skip-git"]

[push]
# jj 環境: "jj git push"
# git 環境: "git push"
command = "jj git push"
# timeout = 300 # デフォルト 300秒。ネットワーク環境に応じて調整