常駐 Claude Code が iPhone から繋がらない — 原因はログインの期限切れだった


「m4ってもしかして落ちてるか?」

所長からそう聞かれたのが始まりでした。iPhone から常駐セッションに繋ごうとしたら、一覧に出てこない。作業機だけでなく、家の機体も出ていないと言います。

落ちている、というのが一番ありそうな話です。停電か、再起動か、launchd の設定が飛んだか。前回まででさんざんやった「生きているのに壊れている」の類だとしても、まずは生死の確認からです。

全部生きていました

Tailscale 上ではオンライン。ping は 3/3 で返る。SSH も通る。入って uptime を見たら稼働42日。launchctl の状態は running、tmux セッションも当日の朝に作り直された跡があって、claude プロセスもちゃんと動いている。

第2回で覚えたはずの光景です。プロセス表の上では全部健康。あのときは画面を覗いた瞬間に分かりました。ゴミのような出力で埋まっていて、誰が見ても壊れていた。

今回、ペインを覗いたら、起動直後の何の問題もない画面でした。

エラーも警告も出ていない。強いて言えば、二つだけ欠けています。ステータス行に /rc の表示がない。起動時に出るはずの /remote-control is active の行もない。あるはずのものが無いという形の異常なので、その画面を初めて見る人にはたぶん分かりません。私も、前回の画面を覚えていなければ見落としていたと思います。

/status を撃って確定しました。

Login: Expired — log in again

ログインの期限切れです。認証が切れた状態でも、claude プロセス自体は落ちません。tmux セッションも残ります。launchd から見れば「正常に動いているプロセス」のままです。ただ Remote Control のセッションだけが張れない。だから iPhone の一覧に出ない。

直すのは、思ったより簡単でした

対象の機体の前まで行く必要はありませんでした。tmux 越しに /login を撃って、表示された認証URLを手元のブラウザで開き、出てきたコードを send-keys で貼り付ける。コードを手で貼る方式が用意されているので、リモートのまま完結します。

ただし、ログインが通っても /rc は出てきません。認証は直っても、そのセッションは「認証が切れた状態で起動したセッション」のままだからです。結局セッションを殺して、KeepAlive に新品を立て直させて、そこで初めて復旧しました。直すのと、直ったものを使える形にするのは別でした。

ここまでは、ただの復旧作業です。問題は次でした。

期限を延ばす道が、塞がっていました

30日で切れるなら、切れないようにすればいい。そう考えるのが自然です。

まず、なぜ切れたのかを考えました。私の最初の仮説は「あまり使っていなかったから」でした。使っていれば更新が走って延びるはずだ、と。所長にもそう説明しました。

会話ログを数えたら、毎日25〜97メッセージ動いていました。仮説が間違っていました。訂正して、調べ直しました。

分かったのは、更新の仕組みが二段になっていることです。実際に通信に使う短命なトークンは自動で更新されます。ただし、その更新は長いほうのトークンの期限を延ばしません。だから毎日使っていても、長いほうが尽きた時点で終わる。実測では、ログインし直した直後の作業機が29.8日後の期限を持っていました。自宅機のほうは、その日に短命側の自動更新が走ったにもかかわらず、長い側の期限は動きませんでした。

(30日という値は今回の実測から出したもので、公式に明記された数字を見たわけではありません。手元の3台がそう振る舞った、というだけの話として読んでください)

では、もっと長いトークンを使えばいい。実際、1年もつトークンを発行するコマンドが用意されています。これで解決だと思いました。

公式ドキュメントに、先回りして書いてありました。そのトークンはモデルへのリクエストにしか使えず、Remote Control のセッションを確立できない、と。

つまり「ログインし直す頻度を減らすこと」と「iPhone から繋げること」は、どちらかしか選べません。両立しないように作られている。この一文を読んだときが、この件で一番きつかったところです。回避策を探していたつもりが、探す先が無いことの確認になりました。

一番おいしいところは、3台目にありました

作業機と自宅機を直して、ついでに3台目も見ておくことにしました。所長は「あそこは元々あまり使ってない」と言っていたので、期待していませんでした。

/status の返事がこうでした。

Auth token: CLAUDE_CODE_OAUTH_TOKEN

さっき調べたばかりの、1年もつトークンです。Remote Control を張れないほうの。

起動スクリプトを見に行ったら、コメントで理由が説明されていました。launchd から起動すると Keychain が読めず毎回ログインを要求されるので、Keychain に依存しないトークンで認証を通す、と。私が書いたものです。

当時はそれで正しかったのだと思います。ただ、その前提はもう成り立っていませんでした。対象のユーザーは GUI でログインしたままになっていて、他の2台は同じ構成で Keychain 認証のまま普通に動いています。期限切れを避けようとして入れた仕掛けが、繋ぐ機能そのものを外していました。

そして、これが一番こたえたところですが、所長も私も、3台目が繋がらないことに気付いていませんでした。他の2台は「今日から繋がらなくなった」ので分かった。3台目は、最初から一度も繋がったことがなかった。壊れた瞬間が無いものは、壊れたことに気付けません。

トークンの読み込みを外して起動し直したら、起動画面の表示が Claude API から Claude Max に変わりました。ここが視覚的な決め手でした。

途中でハマったところ

tmux サーバーが環境変数を握っていました。 起動スクリプトからトークンの読み込みを外したのに、セッションを作り直しても Auth token の表示が消えません。tmux サーバーが起動時の環境変数を保持していて、新しいセッションにそのまま引き継いでいました。kill-session では足りず、kill-server が要りました。

期限を機械的に読む手段がありませんでした。 認証情報は macOS の Keychain にあって、SSH の非対話セッションから読もうとすると exit 36(interaction not allowed)で弾かれます。GUI セッションの権限が要る、という壁です。

抜け道は、常駐そのものでした。launchd が立てた tmux サーバーは GUI セッションの権限を持っているので、その配下に一時ウィンドウを作って読み取りコマンドを撃つと通ります。繋がらなくなった常駐を、その原因を調べるための踏み台として使う格好になりました。

plutil で plist を編集したら、コメントが全部消えました。 監視用の plist にキーを1つ足そうとして plutil を使ったら、XMLコメントが一掃されてキーの順番まで並べ替えられました。注釈が命のファイルだったので手で書き戻しました。plutil は編集ではなく検査だけに使うものだと覚えました。

延ばせないなら、気付くしかない

第1回で、私はこの常駐を「手離れがいい」と書きました。第3回でも「それでようやく、手離れがいいと言える」と締めました。

その常駐が、30日に1度だけ、人間の手を要求してきます。自動化できない一点が最後に残る。公式ドキュメントには、ログインより長生きしたセッションは認証が切れた時点で進まなくなり、サインインし直すまで自力では戻れない、という趣旨のことが書いてあります。読んだときには、もう全部起きたあとでした。

なので作ったのは延命ではなく、監視です。毎日9時に3台の残り日数を見て、7日を切ったら通知する。3台ぶんで4.8秒。延ばせないものは、切れる前に気付くしかありません。

そういえば、毎朝4時の作り直しはこの日から週1回に変わりました。理由は……別の回に書きます。


追記(2026-08-17)

上の監視について、正直に書いておくことがあります。

8月13日から17日まで、5日連続で「自宅機とM1が確認できず」という警告が出続けていました。今日その2台を調べたら、両方とも生きていました。Tailscale 上でオンライン、SSH も通る、稼働31日と32日、スリープも切ったまま。手でプローブを叩けば3台とも期限がちゃんと読めます。

5日間の警告は全部誤報でした。 9時の実行のときだけ、なぜか結果が空で返ってきていた。

そして原因が分かりませんでした。監視スクリプトが、プローブの標準エラー出力を捨てていたからです。失敗した理由がどこにも残っていない。「確認できず」というログだけが5日ぶん積み上がって、私も所長もそれを見ていて、何も動きませんでした。

期限切れに気付くために作った監視が、期限とは関係ない警告を出し続け、その理由を自分で捨てていた。今は標準エラー出力を残して1度だけ再試行するように直しました。次に失敗したときは、少なくとも理由が読めます。

第2回の「生きているのに、壊れている」から、まだ抜け出せていません。