SUZ-LAB

ガードレールを多層で組む — ブランチ保護と git worktree 運用を監査して固めた

ソロ+AI エージェント常用のモノレポで、main のブランチ保護(GitHub Ruleset)と git worktree の運用が本当に妥当かをディープリサーチ報告と突き合わせて監査した記録。報告の推奨をそのまま鵜呑みにせず、この環境(Codespaces / pnpm ハードリンク共有)に本当に効くものだけを適用し、規模が育ってから効くものは Roadmap に後回しした。多層防御(ローカル=利便性 / サーバー側=強制)の設計と、実際に入れたコードを一次情報で残します。

何が問題だったか

このリポジトリは ソロ+AI エージェント常用という、少し特殊な体制で回している。人間が コードを書く量より、Claude Code / Copilot が書く量のほうが多い日もある。だからこそ 「エージェントが main に直接コミットする」「レビュー指摘を自分で解決してマージを通す」 といった習慣的な事故を、仕組みで止めておく必要がある。

すでに手は打ってあった——main はリポジトリ Ruleset で保護し、ローカルには .githooks/pre-commit を置き、worktree はヘルパスクリプト(wt:new / wt:rm)で規約どおりに 作らせている。ただ、それらが本当に妥当か、そしてこの環境に固有の落とし穴を踏んで いないかは、腰を据えて検証していなかった。

そこで、ブランチ保護と worktree 運用に絞ってディープリサーチをかけ、その報告書を 監査のチェックリストとして使い、現状と一つずつ突き合わせた。ポイントは、報告の推奨を そのまま適用しないこと。一般論として正しくても、この repo(Codespaces / pnpm の ハードリンク共有)で本当に効くものだけを入れ、いま効かないものは Roadmap に後回しした。

設計 — なぜ「多層」で組むのか

出発点にした原則はひとつ。ローカルのフックは利便性であって、セキュリティ保証ではない。 git commit --no-verify や GUI クライアント経由で簡単に迂回できる。だから防御は 2 層に分け、 役割をはっきり分離する。

  • サーバー側=強制(迂回不可): GitHub Ruleset。main への直 push 禁止・force push / 削除禁止・PR 必須・未解決レビュースレッドがあるとマージ不可。ここは API 側で拒否されるので、 人間だろうと AI だろうと破れない。
  • ローカル=利便性(手戻り防止): .githooks/pre-commitmain でのコミットを コミットが積まれる前に弾き、「ブランチを切り直せ」と教える。push して初めて Ruleset に 跳ね返される、という無駄な往復をなくすためのもの。

この分離は、少人数体制のレビュー設計にも効いている。承認レビュー数は 0(実質セルフマージに なる承認要求で速度を殺さない)だが、未解決スレッド解決は必須。機械チェックで拾えない 論理・設計の指摘だけをマージゲートにする、という割り切りだ。ただしこれには穴がある—— AI エージェントが人間の指摘スレッドを自分で Resolve してマージを通せてしまう。ここは 仕組みでなく規約で塞ぎ、リポジトリの AGENTS.md に「人手の監査コメントは人間が解決する」と明記した。

やってみたこと — 適用したもの

報告のうち、この環境に実際に効くと検証できたものだけを入れた。

1. extensions.worktreeConfig で hooksPath 上書きを予防する

いちばん効いたのはこれ。AI エージェントのツールが git worktree を自動生成するとき、共有の .git/configcore.hooksPath を勝手に書き換え、設定したフックを丸ごと無効化する不具合が 報告されている。ここでは pre-commit フックがこの経路で死ぬと、main 直コミットのガードが 外れる。

対策として、ルートの prepare スクリプトに extensions.worktreeConfig true を足した。

"prepare": "git rev-parse --git-dir >/dev/null 2>&1 && git config extensions.worktreeConfig true && git config core.hooksPath .githooks || true",

preparepnpm install のたびに走る。各 worktree でも pnpm install は必ず走るので、 万一 core.hooksPath が上書きされても、次のインストールで .githooks に貼り直される (自己修復)。worktreeConfig を立てておくのは、worktree ごとに設定を独立管理できるようにする ための予防線。

2. worktree の pnpm install--prefer-offline

共有ストア(/workspaces/.pnpm-store)は worktree と同じ /workspaces ボリューム上にあるので、 依存はハードリンクで解決される。wt:newpnpm install--prefer-offline を足し、 レジストリの再検証を省いて新規 worktree の準備を数秒で終わらせるようにした。

後回しにしたもの(Roadmap の TODO に落とした)

報告のうち、いま入れる必要はないが規模が育つと効いてくる一手は、無理に前倒しせず Roadmap の TODO として残した。監査の価値は「入れる」判断だけでなく「今はやらない」判断にもある。

  • Lefthook への移行 … いまのシェル製 pre-commit は依存ゼロで軽く、単一のガードには十分。 だがモノレポでリンター・型チェック・整形を並列で走らせたくなったら、Go 製の Lefthook が lefthook.yml 一本でそれを賄える。フックが増える前に、が目安。
  • Ruleset のさらなる堅牢化required_signatures(署名必須)と、CI の自動修正コミットを GitHub API 経由で Verified 署名する仕組み、.github/workflows/* など壊れると痛い経路への CODEOWNERS。いずれも GitHub 側の設定変更が要るので、コードとは別レイヤーの一手として分けた。

検証

コードに触れた分は、壊れていないことを機械的に確かめた。

pnpm typecheck   # 型チェック … パス
pnpm lint        # ESLint … パス
git config --get extensions.worktreeConfig   # → true
git config --get core.hooksPath              # → .githooks

prepare の変更は「pnpm install を一度回すと worktreeConfighooksPath の両方が 設定される」ことを確認。wt:new / wt:rm の挙動(作成 → install → 削除 → prune)も一通り通した。

学び

  • 研究報告は「チェックリスト」であって「手順書」ではない。 一般論として正しい推奨でも、 自分の環境で成り立つ前提かを一つずつ検証してから入れる。鵜呑みにしてコピペすると、この repo に 効かない設定を触って別のリスクを持ち込みかねない。今すぐ効くものだけを入れ、残りは後回しにする 切り分けそのものが監査の成果だ。
  • 強制と利便性を分けると設計が澄む。 破れてはいけないもの(main 保護)はサーバー側の Ruleset に、手戻りを減らすだけのもの(コミット前の警告)はローカルフックに。役割を混同しない。
  • AI 常用ならではの穴を先に塞ぐ。 hooksPath の上書き、レビュースレッドの自主解決——人間だけの チームなら起きにくい事故が、エージェントでは「習慣」として起きうる。仕組みで塞げるものは 仕組みで、塞げないものは規約で明示する。次は Roadmap に落とした Lefthook 化と Ruleset 堅牢化を、 必要になったタイミングで一つずつ倒していく。
#Git#GitHub#ブランチ保護#worktree#pnpm#AI#多層防御

同じ柱のほかのログ

この柱の Roadmap を見る