夜間に走らせている記事生成バッチが、朝見たら落ちていた。
You're out of extra usage · resets Aug 31 at 1pm (Asia/Tokyo)
利用枠を使い切っていた。3日後まで待てない。別のアカウントに切り替えたい。
ところが VS Code の Claude Code 拡張には、アカウントを切り替えるUIがない。 調べて出てくるのは「認証情報を削除して再ログインする」という手順だけだった。往復のたびにブラウザでログインし直すことになる。
認証情報がどこにあるかを調べて、削除するのではなく保管して書き戻すようにしたら、再ログインは各アカウント1回だけで済むようになった。以降は1コマンドで往復できる。
なお、これは自分が持っている複数のアカウントを使い分ける話で、個人用と会社用を行き来するようなケースを想定している。
注意: この記事のスクリプトは認証情報そのものを操作する。手順を誤ると再ログインが必要になる可能性があるため、内容を理解したうえで自己責任で試してほしい。非公開の実装に依存しているので、Claude Code 側の変更で動かなくなることもある。
よく紹介されている手順と、その不満
検索して出てくるのはこの手順だ。
security delete-generic-password -s "Claude Code-credentials"で認証情報を削除- VS Code で
Developer: Reload Window - 認証画面が出るので、別のアカウントでブラウザからログイン
これは確かに動く。動くが、戻るときも同じ手順になる。切り替えるたびにブラウザが立ち上がり、アカウントを選び、承認する。1回2〜3分。日に何度も往復するなら無視できない。
そもそも「削除」しかしていないのが引っかかった。元の認証情報を捨てているから、戻るのに再ログインが要る。 捨てずに取っておけばいいのではないか。
認証情報はどこにあるか
macOS の Keychain に入っていた。
security find-generic-password -s "Claude Code-credentials"
"acct"<blob>="takuo"
"svce"<blob>="Claude Code-credentials"
サービス名が Claude Code-credentials、アカウント名がログインユーザー名。値は -w を付けると取れる。510バイトほどのJSONだった。
構造だけ書くとこうなっている(値は当然伏せる)。
claudeAiOauth.accessToken <108文字>
claudeAiOauth.refreshToken <108文字>
claudeAiOauth.expiresAt 1787909074125
claudeAiOauth.refreshTokenExpiresAt <13文字>
claudeAiOauth.subscriptionType max
claudeAiOauth.rateLimitTier <22文字>
リフレッシュトークンが入っているのが重要だった。アクセストークンの期限が切れていても、これがあれば自動で更新される。つまり保管しておけば、期限を気にせず後から書き戻せる。
方針:捨てずに、別の名前で Keychain に置く
やることは単純だった。
- 保存:
Claude Code-credentialsの値を読んで、Claude Code-credentials::<名前>という別サービス名で書く - 切り替え:
Claude Code-credentials::<名前>の値を読んで、Claude Code-credentialsに書き戻す
security コマンドだけで完結する。
# 保存
SECRET="$(security find-generic-password -s "Claude Code-credentials" -a "$USER" -w)"
security add-generic-password -U -s "Claude Code-credentials::main" -a "$USER" -w "$SECRET"
# 書き戻す
SECRET="$(security find-generic-password -s "Claude Code-credentials::main" -a "$USER" -w)"
security add-generic-password -U -s "Claude Code-credentials" -a "$USER" -w "$SECRET"
-U は「既にあれば上書き」の意味。これがないと2回目で失敗する。
保管先も Keychain の中なので、平文のファイルは一切作らない。 ここは譲らないようにした。トークンをホームディレクトリに .json で置く実装も書けるが、認証情報をわざわざ保護の弱い場所に移す理由がない。
これをスクリプトにまとめて、こう使えるようにした。
claude-account list # 保存済みと現在の状態
claude-account save <名前> # いまのアカウントを保存
claude-account use <名前> # 切り替え
claude-account logout # ログアウト
現在: ログイン中 max / トークン有効(〜08/28 18:24)
保存済みアカウント:
* main max / トークン有効(〜08/28 18:24)
sub max / トークン有効(〜08/28 23:31)
* = 現在ログイン中のもの
* が現在ログイン中のもの。保管した値と現在の値を比較して印を付けているだけで、特別なことはしていない。プラン種別とトークンの期限も出しているので、どちらが使えるかの見当がつく。
初回だけ、2つ目のアカウントを「捕まえる」ために1回ログインが要る。save main → logout → リロード → もう一方でログイン → save sub。ここを通れば以降は不要になる。
安全のために入れたこと
道具として使うものなので、事故らない作りにした。
保存していないアカウントを黙って壊さない。 use や logout の前に、いまログイン中の認証情報がどこかに保管済みかを確認する。保管されていなければ警告して、その場で名前を付けて保存できるようにした。
警告: いまログイン中のアカウントはまだ保存されていません。
このまま切り替えると、再ログインしないと戻れなくなります。
先に保存しますか? 名前を入力(空Enterで保存せず続行):
「切り替えたら戻れなくなった」が起きないようにするのが、このツールで一番大事な部分だと思っている。
security dump-keychain を使わない。 保存済みの名前を列挙するのに最初これを使ったが、項目によっては項目ごとにGUIの許可ダイアログが出る。バックグラウンドで走らせたときに止まるのは困る。名前の一覧だけ別ファイルに平文で持つことにした。名前は秘密ではないので、これで困らない。
確認を挟む。 logout は実際に認証情報を消すので [y/N] を出す。
動作環境
検証した環境は以下のとおり。
| 項目 | バージョン |
|---|---|
| macOS | 26.6.2 (25G83) / Apple M4 / arm64 |
| Claude Code | 2.1.121 |
| VS Code | 1.134.0 |
| シェル | /bin/bash 3.2.57(macOS 標準)で動作確認済み |
| 依存 | なし(security sed date のみ。すべて macOS 標準) |
追加インストールは何も要らない。 Homebrew の bash 5 系も不要
(<<< や ${var:-} は bash 3.2 でも使える)。
最初は JSON を読む部分を python3 -c で書いていたが、やめた。
macOS は 12.3 以降 python3 を標準同梱していない(手元に入っていたのは Xcode Command Line Tools 由来だった)。
素の Mac で動かない道具にはしたくなかったので、sed で2つの値を抜き、date -r でエポックミリ秒を整形するだけにした。jq も使っていない。
sub="$(printf '%s' "$json" | sed -n 's/.*"subscriptionType":"\([^"]*\)".*/\1/p')"
exp="$(printf '%s' "$json" | sed -n 's/.*"expiresAt":\([0-9]*\).*/\1/p')"
when="$(date -r "$((exp / 1000))" "+%m/%d %H:%M")"
相手は機械生成のJSONなので、これで十分だった。
Linux や Windows では動かない。Keychain は macOS 固有のもので、他OSでは
認証情報の保存先が違う。
スクリプト全文
~/.claude/bin/claude-account として保存して実行権限を付ける。
mkdir -p ~/.claude/bin
# 下のスクリプトを ~/.claude/bin/claude-account として保存してから
chmod +x ~/.claude/bin/claude-account
echo 'export PATH="$HOME/.claude/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc
#!/bin/bash
# Claude Code のアカウントを切り替える(macOS / VS Code 拡張・CLI 共通)。
#
# 認証情報は Keychain の generic-password に入っている:
# サービス "Claude Code-credentials" / アカウント <ログインユーザー名>
# このスクリプトは、それを別のサービス名に複製して保管し、書き戻すことで切り替える。
# ★平文ファイルは一切作らない。保管先も Keychain の中。
#
# 使い方:
# claude-account list 保存済みアカウントと現在の状態
# claude-account save <名前> いまログイン中のアカウントを <名前> で保存
# claude-account use <名前> <名前> に切り替える
# claude-account logout ログアウト(次回起動時に再ログインを求められる)
#
# 切り替え後は VS Code で Cmd+Shift+P → "Developer: Reload Window" が必要。
set -euo pipefail
SVC="Claude Code-credentials"
ACCT="$(id -un)"
PREFIX="Claude Code-credentials::"
die() { printf '%s\n' "$*" >&2; exit 1; }
# 現在ログイン中の認証情報を取り出す(無ければ空)。
active_secret() { security find-generic-password -s "$SVC" -a "$ACCT" -w 2>/dev/null || true; }
saved_secret() { security find-generic-password -s "${PREFIX}$1" -a "$ACCT" -w 2>/dev/null || true; }
# Keychain へ書き込む。-U で既存を上書きする。
put() {
# 注: security は値をコマンド引数で受け取るため、実行中の一瞬だけ ps に見える。
# 単一ユーザーの Mac を前提とした割り切り。平文ファイルより安全側。
security add-generic-password -U -s "$1" -a "$ACCT" -w "$2"
}
# 認証JSONから人が読める情報だけ取り出す(トークンは出さない)。
describe() {
# 認証JSONから人が読める情報だけ取り出す(トークンは出さない)。
# ★python や jq に依存させない。macOS は 12.3 以降 python3 を標準同梱していないため、
# 素の Mac でも動くよう sed と date だけで済ませる。
local json sub exp when state
json="$(cat)"
sub="$(printf '%s' "$json" | sed -n 's/.*"subscriptionType":"\([^"]*\)".*/\1/p')"
exp="$(printf '%s' "$json" | sed -n 's/.*"expiresAt":\([0-9]*\).*/\1/p')"
if [ -z "$sub" ] && [ -z "$exp" ]; then
echo "(解析できません)"; return
fi
if [ -n "$exp" ]; then
# ミリ秒 → 秒。date -r は BSD(macOS) の書式。
when="$(date -r "$((exp / 1000))" "+%m/%d %H:%M" 2>/dev/null || echo "?")"
if [ "$((exp / 1000))" -lt "$(date +%s)" ]; then state="期限切れ"; else state="有効"; fi
else
when="?"; state="不明"
fi
echo "${sub:-?} / トークン${state}(〜${when})"
}
# 保存した名前の一覧。★security dump-keychain は項目ごとに GUI の許可を求めて
# 止まることがあるので使わない。名前は秘密ではないので平文で持ってよい。
INDEX="$HOME/.claude/claude-accounts.list"
list_saved() { [ -f "$INDEX" ] && sort -u "$INDEX" || true; }
index_add() { touch "$INDEX"; grep -qxF "$1" "$INDEX" || printf '%s\n' "$1" >> "$INDEX"; }
cmd="${1:-list}"
case "$cmd" in
list)
cur="$(active_secret)"
if [ -z "$cur" ]; then
echo "現在: ログインしていません"
else
echo "現在: ログイン中 $(printf '%s' "$cur" | describe)"
fi
echo
echo "保存済みアカウント:"
found=0
while IFS= read -r n; do
if [ -z "$n" ]; then continue; fi
found=1
mark=" "
if [ -n "$cur" ] && [ "$(saved_secret "$n")" = "$cur" ]; then mark="* "; fi
echo "${mark}${n} $(saved_secret "$n" | describe)"
done <<< "$(list_saved)"
if [ "$found" -eq 0 ]; then
echo " (まだありません。'claude-account save <名前>' で保存してください)"
fi
echo
echo "* = 現在ログイン中のもの"
;;
save)
name="${2:-}"; [ -n "$name" ] || die "使い方: claude-account save <名前>"
cur="$(active_secret)"
[ -n "$cur" ] || die "ログインしていないため保存できません。先に Claude Code にログインしてください。"
put "${PREFIX}${name}" "$cur"
index_add "$name"
echo "保存しました: ${name} $(printf '%s' "$cur" | describe)"
;;
use)
name="${2:-}"; [ -n "$name" ] || die "使い方: claude-account use <名前>"
target="$(saved_secret "$name")"
[ -n "$target" ] || die "'${name}' は保存されていません。'claude-account list' で確認してください。"
# ★保存していない認証情報を黙って壊さない。
cur="$(active_secret)"
if [ -n "$cur" ]; then
known=0
while IFS= read -r n; do
if [ -z "$n" ]; then continue; fi
if [ "$(saved_secret "$n")" = "$cur" ]; then known=1; fi
done <<< "$(list_saved)"
if [ "$known" -eq 0 ]; then
echo "警告: いまログイン中のアカウントはまだ保存されていません。" >&2
echo " このまま切り替えると、再ログインしないと戻れなくなります。" >&2
printf " 先に保存しますか? 名前を入力(空Enterで保存せず続行): " >&2
read -r keep
if [ -n "$keep" ]; then put "${PREFIX}${keep}" "$cur"; index_add "$keep"; echo " 保存しました: ${keep}" >&2; fi
fi
fi
put "$SVC" "$target"
echo "切り替えました: ${name} $(printf '%s' "$target" | describe)"
echo
echo "★ VS Code で Cmd+Shift+P → 'Developer: Reload Window' を実行してください。"
;;
logout)
cur="$(active_secret)"
[ -n "$cur" ] || { echo "すでにログインしていません。"; exit 0; }
known=0
while IFS= read -r n; do
if [ -z "$n" ]; then continue; fi
if [ "$(saved_secret "$n")" = "$cur" ]; then known=1; fi
done <<< "$(list_saved)"
if [ "$known" -eq 0 ]; then
echo "警告: このアカウントは保存されていません。再ログインが必要になります。" >&2
fi
printf "ログアウトします。よろしいですか? [y/N]: "
read -r ans
[ "$ans" = "y" ] || { echo "中止しました。"; exit 0; }
security delete-generic-password -s "$SVC" -a "$ACCT" >/dev/null
echo "ログアウトしました。"
echo "★ VS Code で Cmd+Shift+P → 'Developer: Reload Window' を実行してください。"
;;
*) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' ;;
esac
読むときのポイントを3つだけ。
put()が Keychain への書き込み。-Uが「あれば上書き」で、これが無いと2回目に失敗するuseの中ほどにあるknownの判定が、保存していないアカウントを壊さないための砦list_saved()がsecurity dump-keychainではなくインデックスファイルを読んでいるのは、
GUI の許可ダイアログで止まらないようにするため
限界と注意(正直に書いておく)
security は値をコマンド引数で受け取る。 つまり実行の一瞬だけ ps に見える。単一ユーザーのMacを前提とした割り切りで、平文ファイルを置くよりは安全側、という判断をした。標準入力から読ませる方法があれば教えてほしい。
logout すると実行中のチャットセッションも切れる。 拡張機能が同じ認証情報を使っているので、リロードすると当然そうなる。区切りのいいタイミングでやる必要がある。会話履歴は残るので、再ログイン後に辿れる。
切り替えると夜間バッチも切り替わる。 私は launchd から Claude を headless で走らせているが、これらも同じ Keychain を見ている。切り替えれば全部が新しいアカウントで動く。これは利点でもあり、意図しないと事故にもなる。
Developer: Reload Window を忘れると効かない。 拡張機能が起動時に読んだ認証を掴んだままなので、コマンドだけ打って「切り替わらない」と悩むことになる。スクリプトの出力に毎回この一文を出すようにした。
そのうち不要になるはず。 アカウント切り替えUIは機能要望として上がっているようなので、公式に対応されたらこのツールは捨てる。それまでのつなぎだと思っている。
まとめ
- Claude Code の認証は macOS の Keychain(サービス名
Claude Code-credentials)にある - よく紹介される手順は「削除して再ログイン」だが、削除せず別名で保管すれば往復できる
- リフレッシュトークンごと保管するので、期限切れを気にしなくていい
- 保管先も Keychain にすれば平文ファイルを作らずに済む
- 道具にするなら「保存していないものを壊さない」を最優先で入れる
きっかけは夜間バッチが利用上限で落ちたことだったが、調べてみると認証の置き場所自体は素直で、30分ほどで形になった。「公式にUIがないから諦める」の一歩手前で、どこに何が保存されているかだけ調べてみると案外どうにかなる、という話でもある。


コメント