複数のお客さんのプロジェクトを回していると、特定のorganization用にGithubアカウントを分けないといけないことがある。
今までであればChromeのプロファイル分けて作業すればよかったが、Claude Codeに作業やらせるのに Github CLI(gh) が使えないと大変困る。内容コピペ作業するのなんてだるい。
よって、direnvを使ってGH_TOKENを使ってリポジトリ(=ディレクトリ)ごとに自動で切り替えるようにした。
(多分、こんなことは皆さんご存知だと思うのですが、自分は direnv 知らなくて勉強になったので、備忘録。)
TL;DR
- リポジトリに
cdした瞬間だけghCLI が 別の GitHub アカウントとして動く ようにしたかった。 -
direnv+.envrcでGH_TOKENを export する構成にした。 - ただし
.envrcに トークンを直書きしない。gh auth token --user <name>を使い、すでにghの keyring に入っているトークンを動的に引く。 - 結果として 平文の secret はディスクに残らない。
.envrc自体も.gitignore対象。
やりたかったこと
普段は個人 GitHub アカウント(仮に mainuser)でログインしている gh CLI を、特定の顧客プロジェクト用リポジトリに居る間だけ、別アカウント(仮に workuser)として振る舞わせたい。
-
gh pr createやgh issue listを そのプロジェクト用のアカウント で発火させたい -
git pushの SSH 認証は別管理(~/.ssh/config側で済んでいる)ので、ここでは API トークン側(gh CLI)だけ を切り替えたい - グローバル設定(
gh auth switch)を毎回叩く運用は事故るので避けたい
前提
- macOS(zsh)
- Homebrew 導入済み
- 切り替えたい両方のアカウントは、すでに
gh auth login済みで keyring に保存されている
確認:
$ gh auth status
github.com
✓ Logged in to github.com account mainuser (keyring)
- Active account: true
✓ Logged in to github.com account workuser (keyring)
- Active account: false
両方とも keyring に居る状態がスタート地点。
設計判断
.envrc には 3 つの選択肢 があった。
| 案 |
.envrc に書く内容 |
平文 secret の有無 |
|---|---|---|
| A |
export GH_TOKEN="gho_xxxx..." をベタ書き |
ディスクに残る |
| B |
pass / 1Password CLI 等から取る |
ローカルに secret 管理ツールが要る |
| C |
gh auth token --user workuser で動的に取る |
keyring 任せ、追加ツール不要 |
gh の keyring にすでに 2 アカウント入っていたので C 案 が一番素直。gh auth token は対話なしで token をそのまま標準出力に出すので $() で受けられる。
加えて gh は GH_TOKEN 環境変数があればそれを優先 する。なのでこの env を立てるだけで、gh auth switch を叩かなくてもそのシェルでは自動的に workuser 扱いになる。
手順
1. direnv を入れる
brew install direnv
2. zsh の hook を有効化
~/.zshrc の末尾に追記:
# direnv hook
eval "$(direnv hook zsh)"
source ~/.zshrc するか、ターミナルを開き直す。
3. リポジトリ直下に .envrc を作る
~/work/my-project/.envrc:
# このリポジトリでだけ gh CLI を別アカウント (workuser) で動かす。
# トークンは keyring から動的に取得するので、このファイルには平文 secret を残さない。
# `direnv allow` で承認した後、cd するたびに自動で export される。
if command -v gh >/dev/null 2>&1; then
_token="$(gh auth token --user workuser 2>/dev/null || true)"
if [ -n "$_token" ]; then
export GH_TOKEN="$_token"
fi
unset _token
fi
ポイント:
-
gh auth token --user <name>は対話なしで keyring の token を返す - 失敗時(gh 未インストール / 該当ユーザーが keyring にいない)は 何も export しない。この場合は global の active account にフォールバックする
-
--userを付けないと active account のもの が返ってしまうので、明示する
4. .gitignore に追加
.envrc
.envrc.local
.direnv/
トークンを直書きしない設計でも、.envrc 自体はプロジェクト固有の前提(アカウント名)を含むので、念のためコミットさせない方が安全。チームでこの仕組みを共有したい場合は .envrc.example をテンプレートとしてコミットする運用にすると良い。
5. direnv に許可を与える
$ cd ~/work/my-project
direnv: error /Users/.../my-project/.envrc is blocked. Run `direnv allow` to approve its content
$ direnv allow
direnv: loading ~/work/my-project/.envrc
direnv allow は そのファイルの内容に対する署名のような承認 で、.envrc を編集するたびに再度 allow が必要になる。野良の .envrc を勝手に読まない安全装置。
動作確認
リポジトリ内(.envrc が効いた状態):
$ gh api user -q .login
workuser
リポジトリ外(素の gh):
$ cd ~
$ gh api user -q .login
mainuser
cd ひとつで gh の API 認証が切り替わるようになった。gh pr create / gh issue create 等もそのまま正しい側のアカウントで動く。
なぜ gh auth switch ではダメだったか
gh auth switch は グローバルの active account を書き換える コマンドなので、
- リポを離れても
workuserのまま残る - 別のターミナルにも影響する
- 戻し忘れて main アカウントのつもりで
workuserから PR が出る、という事故が起きる
direnv 経由で 環境変数として スコープさせると、ディレクトリを抜けた瞬間に GH_TOKEN が unset され、global の挙動に戻る。これがやりたかった性質。
git push の認証はどうなるか
今回触ったのは gh の API 認証(GH_TOKEN)だけ。git push の認証経路は別系統で、
- HTTPS で push → credential helper(osxkeychain 等)
- SSH で push →
~/.ssh/configの Host エイリアス
なので、git 側のアカウント分離は別途やる必要がある(多くの人はすでに SSH config で済ませているはず)。本記事の手順で影響を受けるのは gh 系コマンドだけ、というのは大事な前提。
ハマりどころメモ
-
gh auth tokenに--userを付け忘れる: active account のトークンが返るので、結果として何も切り替わらない。 -
.envrcをdirenv allowし忘れる: ディレクトリに入ってもdirenv: error ... is blockedが出るだけで env はセットされない。 -
GH_TOKENをシェルの rc ファイルに global で export している: そっちが優先されて direnv の export が打ち消されるので、もし~/.zshrc等に書いてあれば外す。 -
新規ターミナルで反映されない:
~/.zshrcの hook 追記後にターミナルを再起動していないだけ。
まとめ
-
direnv×GH_TOKEN×gh auth token --userの組み合わせで、リポジトリに居る間だけ gh のアカウントを安全に切り替えられる。 - secret は keyring 任せで、
.envrcには平文トークンが残らない。 - グローバル設定(
gh auth switch)を触らないので、戻し忘れ事故が起きない。
「このリポでだけ別アカウントで API を叩きたい」のような スコープ限定の認証切り替え は同じパターンで応用できるはず(AWS プロファイル切り替えや、OPENAI_API_KEY の出し分け等)。