shunt は、組み込みの anthropic プロバイダーの背後で複数の Claude サブスクリプション OAuth 認証情報をプールできます。Claude Code が x-claude-code-session-id を送る場合、リクエストはセッションスティッキーになります。ヘッダーがないリクエストはプロバイダーごとのラウンドロビンを使います。shunt は各アカウントの上流クォータヘッダーを追跡し、スティッキーなアカウントがモデルに関係するクォータに近づくとプロアクティブにローテーションします。クォータ拒否、認証失敗、上流障害に対しては、リアクティブなフェイルオーバーが安全網として残ります。
プールを設定する
auth = "claude_oauth" を設定し、明示的なアカウントエントリーを追加します。
[providers.anthropic]
kind = "anthropic"
base_url = "https://api.anthropic.com"
auth = "claude_oauth"
# Existing Claude Code credentials file. shunt refreshes and writes it back.
[[providers.anthropic.accounts]]
name = "primary"
credentials = "~/.claude/.credentials.json"
uuid = "00000000-0000-0000-0000-000000000000" # optional
# Long-lived `claude setup-token` value. Used verbatim; not refreshed.
[[providers.anthropic.accounts]]
name = "backup"
token_env = "CLAUDE_BACKUP_OAUTH_TOKEN"
uuid = "11111111-1111-1111-1111-111111111111" # optionalexport CLAUDE_BACKUP_OAUTH_TOKEN='<value from claude setup-token>'
shunt check
shunt run3 つの Claude ログインモードのいずれかでアカウントを保存できます。
# 新しいリフレッシュ可能なログインを作成(デフォルトは自動 localhost callback)。
shunt login claude --name primary --mode oauth
# 現在のリフレッシュ可能な Claude Code ログインをインポート。
shunt login claude --name imported --mode import
# 1 年間・推論専用の setup token を生成して保存。
shunt login claude --name backup --mode setup-tokenTTY で --mode を省略すると、OAuth がデフォルト選択された 3 方式のプロンプトが開きます。非対話入力では従来の import デフォルトを維持します。--long-lived は --mode setup-token の deprecated alias です。Full OAuth は通常、一度限りの 127.0.0.1 callback で完了します。<code>#<state> を貼り付ける場合は --manual を使ってください。ブラウザー起動、callback の bind、または 5 分間の待機に失敗した場合も、shunt は手動貼り付けへフォールバックします。
その後は名前だけのエントリーを使います。
[[providers.anthropic.accounts]]
name = "primary"
[[providers.anthropic.accounts]]
name = "backup"ストアファイルは ~/.shunt/accounts/claude/<name>.json に置かれます。SHUNT_CLAUDE_ACCOUNTS_DIR でディレクトリを上書きできます。設定された accounts リストが空の場合、shunt はストアをスキャンし、有効な JSON アカウントファイルすべてをファイル名順に使います。ストアファイルはプライベートです(Unix では 0600、ディレクトリは 0700)。
リモートのオペレーターは、オプトインの管理 Web サーフェスから、ブラウザーでリフレッシュ可能な Full OAuth アカウントまたは 1 年間の setup token アカウントをプロビジョニングし、プールの現在の健全性を表示できます。既存の credential ファイルのインポートは CLI 専用です。
Full OAuth は新しいリフレッシュ可能な credential を作成します。import は現在の ~/.claude/.credentials.json credential を shunt のストアへコピーします。どちらもリフレッシュ能力を保持し、import は現在のアカウント UUID も記録します。setup-token モードは claude setup-token と同じ 1 年間・推論専用の PKCE フローを実行します。承認後、shunt は表示された認可コードを交換し、トークンとその発行元アカウントの UUID の両方を、トークンを表示せずに保存します。これにより、プールが別のアカウントを選んだときも metadata.user_id.account_uuid の整合が保たれます。同じ名前を再利用すると、そのアカウントのストアファイルは置き換えられます。既存の外部 setup token には、引き続き token_env と明示的な uuid が必要です。
アカウントのフィールド
| フィールド | 必須 | 意味 |
|---|---|---|
name |
はい | 小文字・数字・ハイフンのみからなる一意のラベル。他のソースフィールドがない場合、名前が一致する shunt ストアファイルを解決します。 |
credentials |
使用可能なソースのいずれか 1 つ | Claude Code の .credentials.json 形式のファイル。~/ は展開されます。shunt は期限が近づくとリフレッシュし、リフレッシュ済みトークンをアトミックに書き戻します。 |
token_env |
使用可能なソースのいずれか 1 つ | setup トークンを含む環境変数。値はそのまま使われ、401 の後にリフレッシュできません。 |
uuid |
いいえ | 既存の metadata.user_id.account_uuid を書き換えるための、選択されたアカウントの Anthropic UUID。プールでエイリアスを統合する際の安定したアイデンティティとしても使われます。名前のみのエントリ(ストアスキャンで解決)は選択が行われる前にストアの shuntAccountUuid から自動的に埋められます。credentials または token_env で設定されたエントリのアイデンティティは、空白だけでない uuid が設定されていればその値、そうでなければ name となり、そのアイデンティティが他のエイリアスの明示的な uuid または名前フォールバックのアイデンティティと一致するたびに統合されます。明確で意図的な統合のためには両方のエントリに一致する空白だけでない uuid を設定してください(shunt は明示的な uuid が別のアカウントの名前フォールバックのアイデンティティと偶然一致した場合も警告します)。 |
threshold |
いいえ | ウィンドウ別の値を持たないすべてのウィンドウに適用される、アカウント単位のソフトなクォータしきい値([0.0, 1.0])。低い値を設定すると、早めにローテーションで外れるバックアップアカウントになります。 |
threshold_5h / threshold_7d / threshold_fable |
いいえ | ウィンドウ別のソフトしきい値。それぞれ対応するウィンドウで threshold より優先されます。 |
max_utilization |
いいえ | ウィンドウ別の値を持たないすべてのウィンドウに適用される、アカウント単位のハードキャップ([0.0, 1.0])。対象ウィンドウで使用率がこの値以上のアカウントは選択から除外されます(ハードキャップを参照)。[server.pool] がなくても機能します。 |
max_utilization_5h / max_utilization_7d / max_utilization_fable |
いいえ | ウィンドウ別のハードキャップ。それぞれ該当ウィンドウで max_utilization より優先されます。Fable のキャップは Fable モデルのリクエストにのみ適用されます。 |
priority |
いいえ | スティッキーなアカウントが不健全なときの選択優先度。値が小さいほど優先され、デフォルトは 100 です。 |
disabled |
いいえ | true にすると、設定と管理ダッシュボードには残したまま、アカウントを選択対象から完全に除外します。 |
1 つのアカウントに credentials と token_env の両方を設定しないでください。
選択とプロアクティブなローテーション
x-claude-code-session-idがある場合:安定したハッシュがスティッキーなアカウントを選びます。そのアカウントが利用可能で切り替えしきい値未満なら、shunt はそれを先頭に保ちます。- ヘッダーがない場合、または空の値で送られた場合:プロバイダーごとに独自のラウンドロビンカウンターを持ちます。空の値はひとつの会話ではないため、固定のアカウント 1 つにハッシュされず、このローテーションを共有します。
claude_oauthアカウントプールが処理するすべての上流レスポンスで、shunt は次のヘッダーが存在すれば記録します。anthropic-ratelimit-unified-5h-utilization、anthropic-ratelimit-unified-7d-utilization、anthropic-ratelimit-unified-7d_oi-utilizationanthropic-ratelimit-unified-5h-reset、anthropic-ratelimit-unified-7d-reset、anthropic-ratelimit-unified-7d_oi-reset(Unix 秒)anthropic-ratelimit-unified-status、anthropic-ratelimit-unified-5h-status、anthropic-ratelimit-unified-7d-status、anthropic-ratelimit-unified-7d_oi-status
- デフォルトの切り替えしきい値は
0.98です。5 時間または共有7dの status がrejectedの場合、すべてのモデルでそのアカウントをクォータに近い状態として扱います。7d_oiの status がrejectedの場合、Fable リクエストでクォータに近い状態として扱います。共有 5 時間の使用率がしきい値に達した場合、または適用されるウィークリー使用率がしきい値に達した場合も、クォータに近い状態として扱います。status の拒否判定は、対応する使用率ヘッダーの有無とは無関係に行われます。使用率のしきい値と余裕を計算するとき、Fable モデルは7d_oiの使用率があればそれを使い、なければ共有7dを使います。集約 status は、ウィンドウ別の status が 1 つも記録されていない場合にのみ使われる互換用のフォールバックです。しきい値は、アカウント単位(上記のthreshold*フィールド)またはプール全体(選択のチューニングを参照)で下げられます。 - 5 時間バケットはすべてのモデルに適用されます。Fable のモデル id は、
7d_oi週次バケットの使用率があればそれを使い、なければ共有7dにフォールバックします。それ以外のモデルファミリーは共有7dを使います。Sonnet 専用のヘッダーが今のところ存在しないため、Sonnet も7dを使います。 - クォータに近い、クールダウン中、または
disabledなスティッキーアカウントは、プロアクティブにローテーションで外されます。shunt は、しきい値未満で利用可能なアカウントをpriority(値が小さい順)で優先し、次に適用される週次バケットのリセットが最も早い順で優先して、使わなければ失効するクォータから先に消費します。週次リセットが不明なアカウントが先頭に並びます。その後に利用可能なクォータ接近アカウント、さらに回復が最も早い順のクールダウン中アカウントが続きます。[server.pool]が設定されている場合、週次リセットによるタイブレークはバーンレートの余裕(headroom)に置き換わります(下記を参照)。 - ローカルのクォータやクールダウンの状態で試行順序が空になることはありません。すべてのアカウントがクォータに近い、またはクールダウン中でも、
disabledでなく一時停止もされていない各アカウントは試行順序に残ります。唯一の例外はハードキャップ(max_utilization*、ハードキャップを参照)で、使用率がキャップ以上の間はアカウントを除外します。 - 使用率は自身の観測時刻による上限と、そのウィンドウのリセットの早い方で失効します。観測時刻の上限だけが過ぎ、そのウィンドウのリセットがまだ未来なら、そのウィンドウの使用率とその時刻だけを消去し、その未来のリセットは単独のメタデータとして残します。ウィンドウ別 status は自身の観測時刻による上限と、status の観測時にキャプチャしたリセット境界の早い方で失効し、status の消去時にキャプチャした境界も消去します。後続の usage 更新や reset-only 更新でキャプチャ済み status 境界が移動することはありません。そのウィンドウのリセットが過ぎると、そのウィンドウの使用率とリセットを消去し、キャプチャ済み status 境界も過ぎていれば status も消去します。スタンプ済みの集約 status には独自の寿命があり、無関係なウィンドウの失効ではクリアされません。legacy の unstamped aggregate だけがウィンドウ失効に従います。Fable 以外の成功レスポンスはアカウント全体のクールダウンだけを解除し、Fable 専用のクールダウンはそのまま残します。Fable の成功レスポンスは両方のクールダウンを解除します。
プールの選択・クールダウン・クォータ状態は、プロセスが生きている限り、設定のホットリロードをまたいで維持されます。プロアクティブなローテーションで上流の制限を回避できない場合も、リアクティブなフェイルオーバーは有効なままです。
選択のチューニング([server.pool])
オプションの [server.pool] テーブル(issue #135)は、上記の挙動の上に、ウィンドウ別のソフトしきい値とバーンレートを考慮した順序付けを追加します。テーブルがない場合、選択はこれまでどおり、組み込みの単一しきい値 0.98 のみを使います。
[server.pool]
# hard_threshold = 0.98 # (default) backstop; at/above always sorts last
default_threshold = 0.9 # soft default for every window
default_threshold_5h = 0.95 # per-window overrides
default_threshold_fable = 0.85
burn_rate_avoidance = true # avoid accounts projected to hit a threshold before reset
usage_refresh_seconds = 300 # reconcile out-of-band usage for refreshable accounts
state_path = "shunt-state.json" # persist quota across restarts (warm start)
ramp_initial_concurrency = 2 # storm control: slow-start a freshly switched account
[[providers.anthropic.accounts]]
name = "primary"
priority = 1 # preferred whenever the sticky account is unhealthy
[[providers.anthropic.accounts]]
name = "backup"
threshold = 0.5 # backup: rotate out once half its quota is spent
[[providers.anthropic.accounts]]
name = "spare"
disabled = true # kept configured, never selected- しきい値の解決。 各ウィンドウ
X(5h、7d、fable)について、有効なソフトしきい値は次の順で決まります: アカウントのthreshold_X→ アカウントのthreshold→default_threshold_X→default_threshold→hard_threshold(hard_thresholdを上限としてクランプ)。すべての値は[0.0, 1.0]の使用率の割合で、範囲外の値はshunt checkを失敗させます。 - バーンレートの余裕(headroom)。 各ウィンドウの使用率とリセット時刻から(ウィンドウ長は 5 時間と 7 日に固定されています)、shunt は観測された平均ペースでソフトしきい値に達するまでの時間から、ウィンドウがリセットされるまでの時間を差し引いて予測します。余裕がプラスであれば、そのアカウントは現在のペースでもリセットまで持ちこたえます。同じ
priorityの利用可能なアカウントは、余裕が大きい順に並びます。観測されていないウィンドウは無制限の余裕として扱われます。 - 予測的な回避。
burn_rate_avoidance = trueにすると、予測された余裕がマイナスのアカウントはクォータに近いものとして扱われ、しきい値に達する前にローテーションで外されます。デフォルトはオフです — 余裕による順序付け自体は、この設定に関係なく行われます。 - 全アカウント接近時のガード。 すべてのアカウントがソフトしきい値を超えている(または使い切ると予測される)場合でも、プールが空になることはありません。接近しているアカウントは余裕が大きい順に提供され、
hard_threshold以上のアカウントは引き続き最後にソートされ、その後にクールダウン中のアカウントだけが続きます。 - 適用範囲。 クォータ関連のノブは両方のプールファミリーに作用します: このプールは
anthropic-ratelimit-unified-*ヘッダーから、Codex プールは報告されたx-codex-*5h/7d ウィンドウから動作します(issue #195)。Codex には Fable スコープの7d_oiウィンドウがないためdefault_threshold_fableはそこでは無効ですが、priorityとdisabledはどこでも適用されます。 - 管理プールエンドポイント(
GET /admin/api/pool)は、各アカウントのpriority、disabledフラグ、そして[server.pool]が設定されている場合は現在の余裕予測(秒単位)を報告します。ダッシュボードの状態列は無効化されたアカウントを示します。
ハードキャップ(max_utilization)
ソフトしきい値はアカウントを並べ替えるだけです。ハードキャップは、使用率がキャップ以上である間、アカウントを選択から外します。
[server.pool]
default_max_utilization = 0.95 # 上書きしない限りすべてのウィンドウ
default_max_utilization_5h = 0.9 # ウィンドウ別のデフォルト
[[providers.anthropic.accounts]]
name = "primary"
max_utilization_fable = 0.5 # Fable はこのアカウントの Fable 週次バケットの約 50% で止まり、Opus は使い続ける- キー。 アカウント単位:
max_utilization、max_utilization_5h、max_utilization_7d、max_utilization_fable。[server.pool]では:default_max_utilization、default_max_utilization_5h、default_max_utilization_7d、default_max_utilization_fable。値は[0.0, 1.0]の範囲である必要があり、それ以外は起動とshunt checkを失敗させます。 - 解決順序。 各ウィンドウについて: アカウントの
max_utilization_X→ アカウントのmax_utilization→default_max_utilization_X→default_max_utilization→ キャップなし。キャップはhard_thresholdでは制限されません。アカウントのキャップは[server.pool]がなくても機能し、ストアのスキャンで見つかったアカウント([[providers.anthropic.accounts]]エントリがないもの)にはプールのデフォルトのみが適用されます。 - 除外であり、並べ替えではない。 対象ウィンドウで観測された使用率がキャップ以上のアカウントは選択から除外されます。5h と 7d のキャップはすべてのリクエストに適用され、Fable のキャップは Fable モデルのリクエストにのみ、かつそのアカウントの
7d_oi使用率が観測されたあとにのみ適用されます。使用率が観測されていないウィンドウは除外しません。 - 復帰。 使用率がキャップを下回ると、アカウントは自動的に再び選択対象になります。ウィンドウのリセットまたは失効、より低い観測値、設定リロード時のキャップ引き上げがその経路です。キャップは新しいルーティングを止めるだけで正確な予算ではなく、ディスパッチ済みのリクエストのために使用量を確保することもないため、処理中または同時のリクエストは消費した分だけキャップを超過することがあります。
- 枯渇。 キャップにより選択可能なアカウントがプールに残らない場合、shunt はゲートウェイが生成する HTTP
429rate_limit_error(“all selectable accounts for provider ‘X’ are at their max_utilization cap for this request”)を返し、最も早い復帰時刻が分かる場合はretry-afterを付けます。アップストリームの429と同様に[[upstreams]]のフェイルオーバーチェーンを進めるため、後続のアップストリーム(従量課金の場合もあります)がリクエストを処理し、どれも処理できない場合にのみクライアントは429を受け取ります。ただし、すでに Codex/ChatGPT プールにコミット済み(HTTP200送信済み)のストリーミングリクエストは、後続のアップストリームが処理できない場合、HTTP429やretry-afterヘッダーではなく、ストリーム内の終端 SSEerrorイベントを受け取ります。インバウンドの Codex エンドポイントでは、同じエラーが OpenAI のエラー形式で返ります。 - プロバイダー。 Claude OAuth プール(5h、7d、
7d_oi)と Codex/ChatGPT プール(5 時間と週次のウィンドウ。Fable 用キーはここでは無効)で有効です。Kimi OAuth プールでも、Kimi のアップストリームが使用率ヘッダーを報告する場合はキャップが適用されます。報告は観測されていないため、実際には無効のままで、観測されていないウィンドウは除外しません。Antigravity のクォータはキャップに反映されません。 - 状態表示。 管理ダッシュボードはキャップに達したアカウントを「Capped」、Fable のみのキャップなら「Capped (Fable)」と表示し、設定された各キャップをアカウントの使用量バーとプール表に示します。
GET /admin/api/poolはアカウントごとに解決済みのmax_utilization_5h、max_utilization_7d、max_utilization_fableを報告します(そのウィンドウに適用されるキャップがなければnull)。GET /usageは、利用可能なアカウントがなく、有効(disabledでない)かつ一時停止されていないアカウントのうち 1 つ以上がキャップに達している場合にプール状態cappedを報告します。
Usage-API との突き合わせ
クォータヘッダーは shunt を通過したトラフィックしか反映しません。usage_refresh_seconds は GET /api/oauth/usage をポーリングし、権威ある使用率、報告された各ウィンドウ固有のリセット時刻、使用率の観測時刻を、同じ 5 時間・共有週次(7d)・Fable 専用週次(7d_oi)ウィンドウに適用することで、その差を埋めます。
フィールドが未設定または 0 の場合、ポーリングはオフです。60 未満の正の値は 60 秒に切り上げられます。対象は imported な更新可能アカウントのみで、長期の claude setup-token と token_env アカウントは、トークンがエンドポイントを呼び出せないためスキップされます。間隔は起動時に固定されるため、設定のリロードではポーラーの起動・停止・再調整は行われません。この定期的な補正は各ウィンドウの使用率、リセット、使用率の観測時刻を更新します。ウィンドウ別および集約 status の鮮度と、status の観測時にキャプチャしたリセット境界だけがヘッダー由来のままです。
クォータ状態の永続化
バージョン2の移行では、observed_at_status のない集約 status が、保存された reset_5h、reset_7d、reset_7d_oi のうち最も早いリセットを不変の期限として捕捉します。そのリセットがすでに過ぎている場合は、期限切れのリセット、スタンプのない集約 status、およびそのために合成したスタンプを同じ import で削除します。7 日という妥当な範囲を超える未来のリセットは、起動時刻から 7 日後を上限にします。リセットがなければ起動時刻から 7 日の上限を開始します。既存の v2 スタンプはリセットから再解釈しませんが、通常の import は孤立したメタデータを正規化し、経過したシグナルを失効させ、未来の時刻を起動時刻に補正し、残ったスタンプのない集約には必要に応じて起動時刻を設定します。後続の reset-only または usage 更新は捕捉した期限を延長せず、v3 への書き換えと二回目の復元後も同じ状態を保ちます。
プールのクォータはメモリ上にあるため、再起動はコールドで始まります: 各アカウントは再起動後の最初のレスポンスまで未観測に見え、これにより burn-rate 回避が無効になり、トラフィックでプールが再充填されるまで GET /usage は空を返します。state_path を設定すると、各アカウントのウィンドウごとの使用率とリセットをそのファイルに保存し、プールは最後に観測された状態からウォームスタートします。
このファイルは権威あるソースではなくベストエフォートのキャッシュです — クォータはいずれにせよアップストリームのレスポンスから再導出されるため、ファイルが欠落・陳腐化・破損していてもコールドスタートになるだけで、起動失敗にはなりません。書き込みは非公開の temp ファイル(Unix では 0600)を対象にアトミックにリネームする方式で、クォータが変化したときだけ 15 秒のバックグラウンドタイマーで行われます。書き込みに失敗した場合は dirty 状態を維持し、次の tick で再試行します。クールダウンは保存されず(再起動で失効)、復元されたウィンドウのうちすでにリセットを過ぎたものは、復元時の import 中に最初の選択または snapshot より前に破棄されます。使用率とウィンドウ別 status には独立したリセットなし寿命があり、使用率が失効してもそのウィンドウの未来のリセットは残る一方、キャプチャ済み status リセットは status とともに消去されます。バージョン2のファイルは移行経路でバージョン3に書き直され、バージョン3のリセットなし status はリセットのみの更新後もリセットなしのままです。パスは起動時に固定され、フィールドが未設定なら永続化はオフです。
フェイルオーバーのルール
| レスポンス | 挙動 |
|---|---|
| 2xx | 中継し、健全としてマークします。 |
Fable リクエストで anthropic-ratelimit-unified-7d_oi-status: rejected だけの 429 |
Fable のクォータ枯渇:数値の retry-after で Fable リクエストのみに効くクールダウン(デフォルト 60 秒、1〜3600 秒にクランプ)を適用し、その後ローテーションします。Fable 以外のリクエストは引き続きそのアカウントを使えます。Fable 以外のリクエストで同じ 7d_oi のみの拒否を受けた場合は、アカウント全体のクールダウンを適用します。 |
429 かつ anthropic-ratelimit-unified-5h-status: rejected または -7d-status: rejected |
共有クォータの枯渇:数値の retry-after でアカウント全体をクールダウン(デフォルト 60 秒、1〜3600 秒にクランプ)し、すべてのモデルについてローテーションします。7d_oi も同時に rejected の場合もアカウント全体のクールダウンです。 |
| 単なる 429 | 一時的なスロットル:数値の retry-after の分だけ待機(デフォルト 1 秒、上限 300 秒)し、同じアカウントを 1 回リトライして、そのリトライのレスポンスを中継します。 |
credentials での 401 |
強制リフレッシュして同じアカウントを 1 回リトライ。まだ 401 なら 5 分クールダウンしてローテーションします。 |
token_env またはストア管理の setup トークンでの 401 |
リフレッシュ不可:5 分クールダウンしてローテーションします。 |
| 5xx | 30 秒クールダウンしてローテーションします。 |
| トランスポート障害(接続の切断や TLS エラーなど、レスポンスがない場合) | スロットルやリフレッシュのリトライ中であっても、同じアカウントに一度だけ再送します。再送もトランスポート障害になった場合にのみ 30 秒クールダウンしてローテーションします。タイムアウトは再送しません。再送は POST 全体を送り直すため、まれに同じターンが 2 回実行されることがあります。 |
| その他のステータス | フェイルオーバーせずに中継します。 |
分類はレスポンスボディがストリーミングされる前に行われるため、ストリーム途中の失敗が再送されることはありません。プールがレスポンスを受け取った後に試行を使い切った場合、クライアントは最後の実際の上流ステータスとボディを受け取ります。どの上流レスポンスも受け取る前にすべてのアカウントが失敗した場合、shunt はゲートウェイ自身のエラーを返します。
Anthropic にルーティングされる POST /v1/messages/count_tokens リクエストも同じプールを使います。
Claude Sonnet 5.5 の会話を 1 つのアカウントに保つ
Anthropic は、Claude Sonnet 5.5 が生成した thinking ブロックを、それを生成したアカウントに結び付けます(Preserved thinking)。別のアカウントがそのブロックを送り返すと、API はモデルがそれを見る前に破棄します。リクエスト自体は成功しますが、モデルはそれ以前の推論なしで応答します。以前のモデルの thinking ブロックは影響を受けません。
プールはセッションスティッキーなので、会話は通常 1 つのアカウントにとどまります。次の場合には別のアカウントへ移ります。
- スティッキーなアカウントがクォータに近い、クールダウン中、または無効化されているため、プロアクティブなローテーションがセッションをそのアカウントから移す場合。
- レスポンスがフェイルオーバーのルールに従ってローテーションを引き起こす場合:クォータの 429、リフレッシュで解消しない 401(セットアップトークンはリフレッシュできないため、その 401 はすべてローテーションします)、5xx、または即時の再送でも繰り返すトランスポート障害。
- プール内の異なるアカウントが増減した場合。たとえば
shunt login claudeで追加したストアのエントリは、再起動なしでプールに取り込まれます。スティッキーなアカウントはセッション ID をプール内の異なるアカウント数でハッシュして選ぶため、既存のセッションの多くが別のアカウントに割り当てられます。 - リクエストに
x-claude-code-session-idがなく、ラウンドロビンを使う場合。
移った後の Sonnet 5.5 のターンは、前のアカウントで生成された推論なしで実行されます。失敗は何も起きません。shunt は thinking-binding-controls-2026-08-01 ベータヘッダーを追加しないため、クライアント自身がこのヘッダーを送らない限り、レスポンスはこの破棄を報告しません(shunt はクライアントの anthropic-beta の値を保持し、oauth-2025-04-20 がない場合にのみ追加します)。shunt もこの破棄をログに記録しません。
すべての Sonnet 5.5 リクエストを 1 つのアカウントに載せたい場合は、そのモデル専用に、そのアカウントにスコープした上流を用意します。アカウントのスコープ指定には順序付きの [[upstreams]] 形式を使います(設定の [[upstreams]] セクションを参照)。この形式は 1 つの設定ファイル内で [providers.*] と併用できません。shunt login claude でアカウントを保存し、名前で参照します。
[server]
default_provider = "anthropic"
# 最初に宣言しているため、ここにマッピングされるすべてのモデルで最初に試行されます。
[[upstreams]]
name = "anthropic-sonnet55"
provider = "anthropic"
auth = { mode = "claude_oauth", account = "primary" }
# その他すべてのモデル: 通常のローテーションで Claude ストア全体を使います。
[[upstreams]]
name = "anthropic"
provider = "anthropic"
auth = "claude_oauth"
[[models]]
id = "claude-sonnet-5-5"
[models.upstream_model]
anthropic-sonnet55 = "claude-sonnet-5-5"
anthropic = "claude-sonnet-5-5" # 厳密に固定するなら削除Sonnet 5.5 のリクエストはまず primary に送られます。モデルマップに anthropic の行がある場合、チェーンを進める primary での失敗(クォータの 429 や 5xx など)はプール全体にフォールスルーします。各ターンが使えるのは、そのターンを処理したアカウントで生成された Sonnet 5.5 の推論だけです。そのため、別のアカウントが処理したターンは primary で生成された推論なしで実行されます。ストア全体を使うプールには primary も含まれるため、primary を再試行することもあり、その場合は primary で生成された推論が保たれます。リクエストが失敗するのは、プールが試したすべてのアカウントが失敗した場合だけです。この行がなければ固定は厳密になります:クライアントは代わりに primary のエラーを受け取り、会話を移すものは何もありません。
次のトレードオフに留意してください。
- すべての Sonnet 5.5 セッションが、同じアカウントの 5 時間クォータと週次クォータを消費します。
primaryは、ストア全体を走査するanthropicプールにも残ります。Sonnet 5.5 専用に確保するには、代わりにプールをスコープします。例:auth = { mode = "claude_oauth", accounts = ["backup", "team-2"] }。- クォータ、クールダウン、リフレッシュの状態は上流ではなくアカウントに属します。両方の上流に列挙されたストアのアカウントは、1 つの状態と 1 つのリフレッシュロックを共有します。インラインのアカウントテーブルは、shunt がアカウントを識別できない場合、つまり
uuidがなく、認証情報にshuntAccountUuidもない場合に上流ごとにキー付けされます。そのような認証情報を 2 つの上流にインラインで繰り返すと状態とリフレッシュロックが分かれるため、代わりにストアのアカウントを名前で参照してください。 shunt checkは、スコープに指定した名前がストアに存在するかを検証しません。存在しない名前は、リクエストがプールを解決する時点で失敗します。
リクエストとレスポンスの変更
選択されたアカウントに対し、shunt はクライアントの認証を次で置き換えます。
Authorization: Bearer <selected OAuth token>
anthropic-beta: ...,oauth-2025-04-20受信した authorization と x-api-key の両方を取り除き、oauth-2025-04-20 は存在しないときにのみ追加し、その他のエンドツーエンドのヘッダーは保持します。
プール経由のレスポンスはアカウントを識別します。
x-shunt-account: backup共有ゲートウェイでは中立的なアカウント名を使ってください。このヘッダーは、レスポンスを受け取るすべての認可済みクライアントに、設定されたラベルを公開します。プール枯渇後の最後の上流レスポンスの中継では x-shunt-account は省略されます。
account_uuid
Claude Code は、文字列値の metadata.user_id の中にアカウントメタデータを JSON としてエンコードすることがあります。選択されたアカウントに uuid があれば、shunt は既存の内側の account_uuid をその値で置き換えます。メタデータが存在しない、不正である、account_uuid を欠く、または選択されたアカウントに UUID がない場合は、ボディに手を付けません。欠けているメタデータを注入することはありません。
セキュリティ上の制約
claude_oauth は次の場合にのみ受け入れられます。
- プロバイダーが
kind = "anthropic"である。 base_urlが HTTPS を使っている。- ホストが
anthropic.com、またはapi.anthropic.comのようなそのサブドメインである。
これらの起動時チェックは、OAuth ベアラーがオリジン外へ、あるいは平文で送られるのを防ぎます。HTTPS とホストのチェックはループバックホストでは緩和されます(localhost、127.0.0.1、[::1] など)。ループバックの base_url は平文 HTTP と任意のホストを使えるため、ローカルのデバッグプロキシやモックがトラフィックを受け取れます — ベアラーがオペレーターのマシンから出ることはありません。非ループバックのホストには常に HTTPS + anthropic.com が求められます。共有デプロイでは、claude_oauth がゲートウェイ所有の認証情報を消費するため、[server.auth] も設定してください。クライアントは、すでに送っている ANTHROPIC_AUTH_TOKEN で認証されます(クライアントトークンは x-shunt-token、x-api-key と並んで Authorization: Bearer でも受け付けられます)— プール専用ゲートウェイなら ANTHROPIC_CUSTOM_HEADERS の行は不要です。
ストーム制御(storm control)
[server.pool] ramp_initial_concurrency(デフォルトは無効)を設定すると、アカウントアイデンティティごとの並行受け入れをスロースタートのランプでゲートします。これにより、フェイルオーバーの切り替えで、進行中のすべてのリクエストが切り替え直後のアカウントに一度に殺到することを防げます。トラフィックを受け始めたばかりのアイデンティティは、設定された数までの並行リクエストしか受け入れません。成功レスポンスごとに許容量が倍増し、フェイルオーバーはランプをリセットし、拒否されたリクエストは選択順で次のアカウントに回されます(最後の候補は常に試行されます)。[server.pool] を参照してください。
実装の挙動は KarpelesLab/teamclaude と、出荷されている Claude Code バイナリを参考にしています。shunt は teamclaude へのランタイム依存を持ちません。