2026-08-05 追記——公開の約1時間後に、症状が消えました
claude remote-control(サーバーモード)が、そのまま通るようになりました。CLIのバージョンは 2.1.220 のままで、こちらは何も触っていません。 つまり変わったのはサーバー側です。
ただし Issue はまだ閉じられておらず、関連する報告(#78453)には「自動接続は失敗するが手動で叩き直すと繋がる」という時間によって成否が変わる挙動も書かれています。恒久的に直ったと断定はできません。
なので、この記事は「もう関係ない昔話」ではなく、また401が出たときの回避策と切り分け手順として残します。以下は当日そのままの記録です。
先に結論
Claude Code の Remote Control(スマホやブラウザから手元のセッションを操作する機能)が、この401で起動しなくなったとき。
Error: Registration: Authentication failed (401): Missing Authorization header.
Please provide an OAuth token as a Bearer token.. Remote Control is only available
with claude.ai subscriptions. Please use `/login` to sign in with your claude.ai account.
サブコマンドをやめて、フラグのほうを使えば動きます。
# ダメなほう(サーバーモード)
claude remote-control
# 動くほう(インタラクティブモード)
claude --remote-control
これは Claude Code 側の不具合として報告済みで(anthropics/claude-code #83976)、契約にもログイン状態にも問題はありません。
ここで記事を閉じてもらってかまいません。以下は「なぜそこに辿り着くまでに遠回りしたのか」の記録です。同じ形のエラー文に出会ったときの読み方として、たぶん一般化できます。
エラー文が親切すぎた
もう一度エラーの最後の一文を見てください。
Remote Control is only available with claude.ai subscriptions. Please use
/loginto sign in with your claude.ai account.
「claude.ai のサブスクリプションが必要です。/login でサインインしてください」。
これはとても親切です。そして、とても余計でした。私はこれを読んで「契約かログインの問題だな」と判断し、その線で調べ始めてしまったからです。
まず確認したのは自分の契約とトークンです。
claude auth status
{
"loggedIn": true,
"authMethod": "claude.ai",
"apiProvider": "firstParty",
"subscriptionType": "max"
}
ログイン済み、claude.ai 認証、Max プラン。全部正常です。しかも同じ認証情報で、普通の対話セッションは何の問題もなく動いています。
それでも私は「トークンの有効期限が切れかけているのでは」と考えました。実際、Keychain に入っているアクセストークンの寿命は8時間ほどで、ちょうど境目の時間帯でした。もっともらしい仮説でした。そして完全に外れていました。
デバッグログが3行で仮説を殺した
思いつきで潰していくのをやめて、ログを取ります。Claude Code には --debug-file があって、これを付けるだけで内部の通信ログがファイルに落ちます。
claude remote-control --debug-file ~/Desktop/rc-debug.log
出てきたのは14行だけ。そのうち意味があるのはここです。
[bridge:init] apiBaseUrl=https://api.anthropic.com sessionIngressUrl=https://api.anthropic.com
[bridge:api] POST /v1/environments/bridge bridgeId=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
[bridge:api] Registration: 401 received, attempting token refresh
[bridge:api] Registration: Token refreshed, retrying request
[bridge:api] Registration: Retry after refresh also got 401
3行目から5行目を声に出して読むと、仮説がその場で死ぬのが分かります。
- 401を受け取った
- トークンの再取得に成功した
- その新品のトークンでも、また401だった
つまり Claude Code は、401を食らったら自分でトークンを取り直して1回リトライする作りになっている。「トークンが古い」という原因は、そもそもクライアントが自力で解決してしまうわけです。私が疑っていた線は、最初から候補ですらありませんでした。
ここが最初の教訓です。
自動リトライが実装されているなら、「期限切れ」は容疑者リストから外してよい。
エラーを見た瞬間に「トークンかな」と思うのは反射としては正しいのですが、その反射が正しいかどうかはクライアント側の実装を見れば分かる。ログ3行で確定できることに、私は再ログインを2回も費やしました。
「最後の一文」の正体をコードで確かめる
ログで分かったのは「原因はここではない」までで、まだ「じゃあ何なのか」が分かりません。となると、あの親切なメッセージをもう一度疑うことになります。
Claude Code の実体は、npm でインストールされる260MB超のネイティブバイナリです。中身は JavaScript がほぼそのまま入っているので、grep で該当箇所を探せます。
B=~/.npm-global/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe
LC_ALL=C grep -a -o -b "Remote Control is only available" "$B"
オフセットが取れたら、その周辺をそのまま読み出します。
python3 - <<'EOF'
p = "/Users/xxxx/.npm-global/lib/node_modules/@anthropic-ai/claude-code/bin/claude.exe"
f = open(p, "rb")
f.seek(246051545 - 3000)
print(f.read(6000).decode("utf-8", "replace"))
EOF
出てきたのが、レスポンスのステータスごとに例外を投げ分けている関数でした。読みやすく整形するとこうです。
switch (status) {
case 401:
throw new BridgeFatalError(
`${name}: Authentication failed (401)${msg ? `: ${msg}` : ""}. ${SUBSCRIPTION_NOTICE}`,
401, type
);
case 403:
throw new BridgeFatalError(`${name}: Access denied (403)... Check your organization permissions.`, 403, type);
case 404:
throw new BridgeFatalError(`${name}: Not found (404). Remote Control may not be available for this organization.`, 404, type);
}
SUBSCRIPTION_NOTICE が、例の「claude.ai のサブスクリプションが必要です」です。
401なら中身に関係なく、機械的に末尾へ連結されている。
つまりあの一文は、診断結果ではなく定型の案内文でした。私は署名欄を病名だと思って読んでいたわけです。
ついでに分かったのが、本当にトークンが1つも見つからなかったときは別のメッセージ(You must be logged in to use Remote Control.)になる、ということ。今回それが出ていない以上、クライアントはトークンを見つけて Authorization: Bearer ... を付けて送っている。それでもサーバーは「ヘッダがない」と言う。ここで手元の問題である可能性はほぼ消えました。
2つ目の教訓です。
エラー文は「変わる部分」と「毎回付く部分」に分けて読む。 分からなければ、それを出している側のコードを見に行けばいい。
念のため、手元を全部潰す
とはいえ「ほぼ消えた」で報告するのは気が引けるので、疑える範囲を機械的に潰しました。同じエラーに当たった人がなぞれるように並べておきます。
| 疑ったもの | 確かめ方 | 結果 |
|---|---|---|
| トークンの鮮度 | Keychain の expiresAt を見る/ログのリフレッシュ成否 |
シロ |
| スコープ不足 | 認証情報の scopes を見る |
シロ(user:sessions:claude_code あり) |
| 認可そのもの |
claude auth login で入り直す |
シロ(スコープも期限も更新されたが同じ401) |
| APIキーが優先されている |
ANTHROPIC_API_KEY / apiKeyHelper / primaryApiKey
|
すべて未設定 |
| プロキシがヘッダを剥がしている | 環境変数と scutil --proxy
|
どちらも設定なし |
| 組織ポリシーで無効化 |
managed-settings.json の有無 |
ファイルなし |
| Keychain の認証情報が重複 | 該当サービス名の項目を数える | 1件のみ |
| パスに非ASCII文字(日本語)が入っている | 作業ディレクトリを見る | 後述の理由で除外 |
Keychain の中身はこう見られます(トークンそのものは出さず、長さと項目だけ)。
security find-generic-password -s "Claude Code-credentials" -w | python3 -c "
import sys, json
d = json.load(sys.stdin)['claudeAiOauth']
print('scopes:', d['scopes'])
print('accessToken length:', len(d['accessToken']))
"
全部シロ。ここでようやく、自分の環境を疑うのをやめました。
オチ:同じ人が、前日にいた
手元が全部シロなら、次にやることは「同じ症状の人を探す」です。
gh search issues --repo anthropics/claude-code "remote-control 401" --limit 20
一番上に出てきたのが、前日に立ったばかりのIssueでした。
claude remote-control(server mode) fails registration with 401Missing Authorization headerwhileclaude --remote-control(interactive) works — same account, same machine
タイトルが、そのまま回避策になっています。
中を開くと、貼られているデバッグログが私の手元のものとほぼ一行ずつ同じでした。潰した項目のリストまで同じ。しかも報告者の環境は WSL2 の Ubuntu で、私は macOS。これで「日本語パスが悪いのでは」という最後の未検証項目も消えました(相手のパスは英数字だけです)。
同じ形の報告は他にもありました。
| Issue | 環境 | どこで落ちるか |
|---|---|---|
| #83976 | WSL2 / macOS | 登録(Registration) |
| #61551 | Windows | 登録(Registration) |
| #78453 | macOS | ブリッジの認証情報取得 |
| #30093 / #30102 | — | 接続後のポーリング |
OSもバージョンもばらけています。自分の環境が特別なのではなく、機能のほうが不安定だったという話でした。
3つ目の教訓、というより自戒です。
「手元で潰せる項目が尽きた」と感じるより前に、一度は外を検索したほうが早い。
私は先にバイナリを掘りました。掘ったこと自体は無駄ではなく、「あの一文は定型文」という確信はそこでしか得られませんでした。ただ、順番としては gh search issues が先でよかった。片道1コマンドです。
サーバーモードとインタラクティブモード
回避策が効く理由は、そもそも2つが別物だからです。
claude remote-control |
claude --remote-control |
|
|---|---|---|
| 形 | サブコマンド | フラグ |
| 動き | 常駐サーバーとして立ち、外から複数のセッションを起こせる | いま開いたセッション1本が、外から操作できるようになる |
| 当日の状況 | 登録で401(前述のIssue) | 動く |
スマホから手元の作業を続けたいだけなら、フラグのほうで足ります。私は VS Code のステータスバーにボタンを置いてこれを叩いていたので、その1行を書き換えて終わりでした。
// 変更前
terminal.sendText('claude remote-control --permission-mode plan');
// 変更後
terminal.sendText('claude --remote-control --permission-mode plan');
おまけ:ログに紛れていた別の警告
調べている最中、401とは関係ない行がログに混ざっていました。
[WARN] Keychain payload (138251B JSON) exceeds security -i stdin limit; using argv
認証情報が138KBあるという警告です。中を数えたら、MCPサーバーごとのOAuth記録が255件溜まっていました。本体の認証情報はわずか507バイト、アクセストークンに至っては108文字です。
mcpOAuth 141752 bytes (255 entries)
claudeAiOauth 507 bytes
今のところ実害はありませんが、security コマンドに標準入力で渡せる上限を超えて、コマンドライン引数経由に切り替わっている状態です。増え続ける構造なので、いずれどこかで詰まります。エラーを追いかけているときのログには、探していないものも落ちているという良い例でした。
まとめ
明日から使える形にすると、こうなります。
- Remote Control が401で起動しないなら、
claude remote-controlをclaude --remote-controlに変える - エラー文は「変わる部分」と「毎回付く部分」を分けて読む。最後の一文が定型の案内文であることは、けっこうある
-
--debug-fileを取る。思いつきで潰すより、ログ3行のほうが速い - クライアントが自動リトライするなら、「期限切れ」は容疑者から外す
- 手元を全部潰す前に、一度は Issue を検索する
私はこの順番を守れなくて、いらない再ログインを2回やりました。あなたはやらなくて済みます。