Claude Codeのghコマンド権限問題をPATなしで解決する — ghroラッパーの作り方
Claude Codeでgh CLIの読み書き権限を分離する方法。Fine-grained PATを使わず、通常のgh auth loginの認証を共有しつつ書き込みサブコマンドだけブロックするラッパーghroを作る。Organization repoにも対応。
Claude Codeのghコマンド権限問題をPATなしで解決する — ghroラッパーの作り方
TL;DR
Claude Codeで gh コマンドを使うと、読み取り操作でも毎回権限確認が出る。PATでプロファイル分離するアプローチはOrganization repoだとauthorize手続きが必要になる。通常の gh auth login の認証を共有しつつ、書き込みサブコマンドだけスクリプトでブロックするラッパー ghro を作ることで、PATなし・設定なしで読み書きの分離ができる。
きっかけ
この記事は shibayu36さんの「Claude Codeのgh コマンド権限問題の解決方法」 を読んで、自分の環境でも試してみたところから始まっている。shibayu36さんのアプローチはFine-grained PATで権限を絞った別プロファイルを作るもので、仕組みとしてきれいだった。ただ、Organization repoを扱う自分の環境だとPATのauthorize周りで一手間あったので、別の方法を考えた。
問題
Claude Codeの開発でGitHub CLIの gh コマンドを使う場面は多い。PR情報の確認、Issue一覧の取得、ファイル内容のfetchなど。ただ、Claude Code経由で gh を呼ぶと毎回権限の確認プロンプトが出る。
全体を許可してしまえば楽だが、Issue作成やPRマージのような書き込み操作まで素通りしてしまう。かといって毎回ポチポチ許可するのも面倒。
PATプロファイル分離の課題
shibayu36さんのアプローチでは GH_CONFIG_DIR 環境変数を使って、readonly専用のghプロファイルを別ディレクトリに作る。
bash# readonly専用プロファイルにPATでログイン GH_CONFIG_DIR=~/.config/ghro gh auth login --with-token <<< "<your-token>"
bash#!/bin/sh # ghro ラッパー GH_CONFIG_DIR=~/.config/ghro exec gh "$@"
シンプルで良い方法だが、運用してみると一つ困る点があった。
Fine-grained PATはOrganization所属のリポジトリに対して、Org管理者によるauthorizeが必要になる。 個人リポジトリだけなら問題ないが、仕事で使うOrganizationのリポジトリを扱う場合、PATの追加承認フローを踏まないといけない。チームの管理ポリシーによってはFine-grained PATを許可していないケースもある。
認証は共有のまま、コマンドの入口でブロックする
考え方を変えた。
- 認証はいつもの
gh auth loginで取得したトークンをそのまま使う - readonly制約はGitHub API側ではなく、ラッパースクリプト側で実現する
- 書き込み系のサブコマンド(
pr create,issue create,api -X POST等)をスクリプトで弾く
PATの権限で制限するのではなく、コマンドの入口で制限する。
実装
ghroラッパースクリプト
~/bin/ghro として以下を配置する。
bash#!/bin/sh # ghro - gh readonly wrapper # 通常のgh認証を使用しつつ、書き込み操作をスクリプトレベルでブロックする # PATは不要。Organization repoも通常通りアクセス可能 reject() { echo "ghro: '$*' is a write operation. Use 'gh' instead." >&2 exit 1 } case "$1" in api) for arg in "$@"; do case "$arg" in POST|PUT|PATCH|DELETE) reject "gh api ($arg method)" ;; esac done ;; pr) case "$2" in create|merge|close|reopen|edit|comment|review) reject "gh pr $2" ;; esac ;; issue) case "$2" in create|close|reopen|edit|comment|delete|transfer|pin|unpin) reject "gh issue $2" ;; esac ;; repo) case "$2" in create|delete|edit|fork|rename|archive|unarchive) reject "gh repo $2" ;; esac ;; release) case "$2" in create|delete|edit|upload) reject "gh release $2" ;; esac ;; gist) case "$2" in create|edit|delete) reject "gh gist $2" ;; esac ;; esac exec gh "$@"
実行権限を付与して配置する。
bashchmod +x ~/bin/ghro
CLAUDE.mdへの使い分けルールの記載
プロジェクトの CLAUDE.md(またはグローバルの ~/.claude/CLAUDE.md)に以下を書く。
markdown### GitHub CLI readonly分離(ghro) - readonly操作(PR情報取得、Issue閲覧、ファイル内容取得など)には `ghro` コマンドを使用する - 書き込み操作(PR作成、Issue作成、コメント投稿、マージなど)には通常の `gh` コマンドを使用する - GitHub上のファイル内容取得時は WebFetch ではなく `ghro api` を優先使用する
Claude Codeは CLAUDE.md の指示に従って動くので、これだけで読み取り操作には ghro、書き込み操作には gh を使い分けてくれる。
動作確認
実際にコマンドを叩いてみると、こうなる。
bash# readonly操作 → 通過してghが実行される $ ghro pr view 123 --repo myorg/myrepo # (PR情報が表示される) $ ghro issue list --repo myorg/myrepo # (Issue一覧が表示される) $ ghro api repos/myorg/myrepo/pulls # (GETリクエストが実行される) # 書き込み操作 → ブロックされる $ ghro pr create ghro: 'gh pr create' is a write operation. Use 'gh' instead. $ ghro issue create ghro: 'gh issue create' is a write operation. Use 'gh' instead. $ ghro api -X POST /repos/myorg/myrepo/issues ghro: 'gh api (POST method)' is a write operation. Use 'gh' instead.
Claude Codeから使う場合、ghro を権限プロンプトで一度「許可」すれば以降は確認なしで通る。書き込みが必要な場面では gh を使うので、そのときだけ確認が出る。読み書きの分離が自然にできる。
2つのアプローチの比較
| PATプロファイル分離 | コマンドレベルブロック | |
|---|---|---|
| 追加トークンの管理 | 必要 | 不要 |
| Organization repo | Org管理者のauthorize必要 | そのまま使える |
| セキュリティの制約レイヤー | GitHub API側(トークン権限) | スクリプト側(コマンドフィルタ) |
| トークン漏洩時のリスク | 低い(readonly権限のみ) | 通常の gh と同じ |
| セットアップの手間 | PAT作成 + プロファイル設定 | スクリプト1つ配置するだけ |
セキュリティ制約のレイヤーが違うところがトレードオフになる。PATアプローチはGitHub側で権限が絞られるのでトークンが漏れても安全だが、コマンドレベルブロックはあくまでスクリプトのフィルタなので、gh コマンドを直接叩けば書き込みできてしまう。
ただ、Claude Codeの利用シーンにおいては「うっかり書き込み操作が素通りしないようにする」のが主目的なので、コマンドレベルのブロックで十分だと思っている。
ラッパースクリプト1つの話なので、自分の運用に合わせてブロック対象を増減させるのも簡単。workflow の操作も制限したければ run、disable、enable をブロックリストに足すだけで動く。