はじめに:PRを開くたびに「前回の文脈」を説明し直していた
当社(合同会社ジョインクラス)は社員ゼロで、launchd の定期ジョブ17個と Claude Code で業務の大半を回しています。開発もほぼ Claude Code 経由で、PR は Claude に作らせることが多いです。
困っていたのは 翌朝のレビュー対応 でした。レビューコメントが付いたPRを直そうとすると、新しいセッションで「このPRは何のためで、どこまでやって、なぜこの設計にしたか」を毎回説明し直す必要がありました。説明が雑だと、AIが前日に却下した案を平気で再提案してきます。
これを解決するのが claude --from-pr です。本記事では、
-
--from-prの基本と、効く条件・効かない条件 - 当社の実スクリプト(GitHub監視ジョブ)をベースにした「レビュー待ちPRを朝に一覧化し、再開コマンドまで出す」実装
- ハマりどころと検証方法
をまとめます。
--from-pr とは
--from-pr は、GitHub の PR に紐づいた Claude Code セッションを再開する オプションです。
# PR番号で再開
claude --from-pr 123
# PR URL でも可
claude --from-pr https://github.com/JOINCLASS/ai-ceo-framework/pull/123
ポイントは「紐づいた」の部分です。Claude Code のセッション内で gh pr create を実行してPRを作ると、そのセッションがPRとリンクされます。--from-pr はそのリンクをたどって、PRを作ったときの会話履歴ごと 復元します。
| 状況 |
--from-pr の挙動 |
|---|---|
Claude Code セッション内で gh pr create したPR |
作成時のセッションが再開される |
| 人間がブラウザで作ったPR | 紐づくセッションがないので再開できない |
| 別マシンで作ったPR | セッションはローカル保存のため、そのマシン以外では再開できない |
つまり 「PRはClaude Codeの中で作る」を運用ルールにしておくこと が前提です。
Step 1:PR作成をClaude Code内に寄せる
当社では CLAUDE.md に以下を書いています(抜粋)。
## Git運用
- PRは必ずこのセッション内で `gh pr create` を使って作成する(--from-pr で再開するため)
- PR本文に「設計判断」と「却下した案」を必ず書く
- git push --force は禁止
「却下した案」をPR本文に残すのは、--from-pr で会話は戻っても、長い会話の後半では古い判断が埋もれやすいためです。PR本文は人間のレビュアーにも効くので一石二鳥です。
git push --force 禁止は、過去にAIが force push してブランチを消した事故の再発防止です。--from-pr で再開したセッションが「履歴をきれいにしましょう」と rebase + force push を提案してくることがあるので、ここは明示しておくのが安全です。
Step 2:朝にレビュー待ちPRを一覧化する(実スクリプトベース)
当社には毎朝8:00に走る auto-github-monitor.sh があり、gh api でStar/Fork/Issueの増減を取ってSlackに投げています。この骨格(gh + jq + 前回状態との比較 + Slack通知)をそのまま流用して、変更要求が付いた自分のPRを拾い、再開コマンド付きで通知する ジョブを足しました。
#!/bin/bash
# レビュー対応待ちPRを一覧化し、--from-pr の再開コマンドをSlackに通知
# launchd: com.joinclass.auto-pr-resume(毎日8:05 JST)
set -uo pipefail
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
export HOME="/Users/kyoagun"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
LOG_FILE="$PROJECT_ROOT/.company/scripts/logs/auto-pr-resume.log"
REPO="JOINCLASS/ai-ceo-framework"
notify_slack() {
local msg="$1"
if [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
curl -sf -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg text "$msg" '{text: $text}')" \
>/dev/null 2>&1 || true
fi
}
echo "$(date): === PR再開チェック開始 ===" >> "$LOG_FILE"
# 自分が作者で、変更要求 or 未レビューのオープンPR
PRS=$(gh pr list --repo "$REPO" --author "@me" --state open \
--json number,title,reviewDecision,url \
--jq '.[] | select(.reviewDecision == "CHANGES_REQUESTED" or .reviewDecision == "REVIEW_REQUIRED")
| "#\(.number) [\(.reviewDecision)] \(.title)\n → claude --from-pr \(.number)"' \
2>/dev/null || true)
if [ -z "$PRS" ]; then
echo "$(date): 対応待ちPRなし" >> "$LOG_FILE"
exit 0
fi
notify_slack "🔁 [レビュー対応待ちPR]\n${PRS}\n\n作業ディレクトリ: ${PROJECT_ROOT}"
echo "$(date): 通知送信" >> "$LOG_FILE"
Slackにはこう届きます。
🔁 [レビュー対応待ちPR]
#142 [CHANGES_REQUESTED] feat: 承認キューの期限切れ自動エスカレーション
→ claude --from-pr 142
朝はこの1行をコピペするだけで、前日の会話の続きからレビュー対応を始められます。
Step 3:launchd に登録する
macOS では cron がフルディスクアクセスの制約で全滅した経験があり、当社の定期ジョブは全て launchd です。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.joinclass.auto-pr-resume</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/kyoagun/workspace/one-ceo/.company/scripts/auto-pr-resume.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict><key>Hour</key><integer>8</integer><key>Minute</key><integer>5</integer></dict>
<key>EnvironmentVariables</key>
<dict><key>SLACK_WEBHOOK_URL</key><string>https://hooks.slack.com/services/XXX</string></dict>
</dict>
</plist>
cp com.joinclass.auto-pr-resume.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.joinclass.auto-pr-resume.plist
# 即時に1回動かして確認
launchctl start com.joinclass.auto-pr-resume
tail -n 5 /Users/kyoagun/workspace/one-ceo/.company/scripts/logs/auto-pr-resume.log
ハマりどころ
1. 作業ディレクトリが違うと再開できない
セッションはプロジェクト(ディレクトリ)単位で保存されます。PRを作ったリポジトリのルートで claude --from-pr を実行してください。Slack通知に PROJECT_ROOT を入れているのはこのためです。worktree で作業した場合は、その worktree のパスで実行します。
2. launchd では gh が見つからない / 認証されていない
launchd は対話シェルの PATH を引き継ぎません。スクリプト冒頭で PATH と HOME を明示しないと gh: command not found か未認証扱いになり、|| true で握りつぶされて「対応待ちPRなし」と誤判定します。ログが毎日「なし」なら、まず以下を疑ってください。
env -i HOME="$HOME" PATH="/opt/homebrew/bin:/usr/bin:/bin" gh auth status
3. 人間が作ったPRは再開できない
ブラウザでPRを作ると紐づくセッションがありません。その場合は通常どおり claude を起動し、「PR #142 のレビューコメントに対応して」と頼みます(gh pr view 142 --comments で読ませる)。
4. 再開後のセッションは古いブランチ状態を前提にしている
前日からmainが進んでいると、会話上の前提とコードが食い違います。再開直後に必ず次を実行させています。
git fetch origin && git status && gh pr view --comments
検証方法
- Claude Code 内で小さな変更を加え、
gh pr createでPRを作る - セッションを終了する
- GitHub上でそのPRに「Request changes」を付ける
-
bash auto-pr-resume.shを手動実行し、Slackにclaude --from-pr <番号>が出ることを確認 - 出てきたコマンドを実行し、PR作成時の会話が残っていること(「さっき〇〇の案を却下した理由は?」と聞いて答えられるか)を確認
5の質問で正しく答えられれば、文脈ごと復元できています。
おわりに
導入して一番変わったのは、レビュー対応の「立ち上がり」です。以前は説明し直しに毎回10分ほどかかり、しかも説明が漏れると却下済みの案が再提案されていました。--from-pr + 朝の通知にしてからは、コマンドを1行貼るだけで続きから始められます。
一人会社では、人間の判断時間が一番希少なリソースです。AIに「前回の続き」を覚えさせておく仕組みは、地味ですが効きます。
PR作成・レビュー・再開までを含めた、AIを前提にした開発プロセス全体の設計は、書籍『AI駆動開発ライフサイクル(AI-DLC)の教科書』にまとめています。