権限の自動判定が60秒で切れた ローカルLLMを判定役にした代償

10月22日、MacBook AirのClaude Codeから、ミニ(Mac mini)のローカルLLMにつないで作業していました。そのとき、ミニへのsshが「Bashの安全性を自動判定できない」という趣旨の理由で止められました。
一時的な障害かと思いましたが、ミニの ollama.log を数えると、そうではありませんでした。/v1/messages への要求のうち、10/20〜10/22の500は71件で、そのうち69件が、60秒で揃って切れていました。
原因は、権限の自動判定(autoモード)の判定役が、作業しているのと同じローカルモデルになっていたことです。判定のたびに約3.4万トークンを読ませる設計で、このミニでは毎秒205〜340トークンしか読めないので、60秒に収まりません。
何をつないでいたか
Air側のClaude Codeを、SSHトンネルを通して、ミニのOllamaにつないでいます。Ollamaは、Anthropic形式の /v1/messages を話します(公式ドキュメントに「Anthropic Messages APIのサブセットに対応」とあります)。
入口の起動スクリプト(mini-claude.sh、ミニ用にClaude Codeを起動するシェルスクリプト)は、claude --bare で起動し、モデルを決める環境変数5つ(ANTHROPIC_MODEL、ANTHROPIC_DEFAULT_OPUS_MODEL、ANTHROPIC_DEFAULT_SONNET_MODEL、ANTHROPIC_DEFAULT_HAIKU_MODEL、CLAUDE_CODE_SUBAGENT_MODEL)を、すべてローカルのモデル名にしていました。別のモデル名が呼ばれても、ローカルに落とすためです。
ミニには、Qwen3.8 27B(GGUF)とgemma4 26B(MLX)が入っています。
何が起きたか
sshを実行しようとすると、自動判定が「安全性を判定できない」とし、モデル名つきで止めました。公式ドキュメントでは、この種のメッセージは「判定役への要求が失敗した」ことを指し、「たいていは一時的」と書かれています。 しかし、ログを見ると、1回きりではありませんでした。ミニの時計で10月22日の1日分だけで、60秒で切れた500が57件ありました。
ログを数えた
ollama.log から、/v1/messages への要求を、日付ごと・状態ごとに数えました。モデルは区別していません。日付は、ミニの時計(米西海岸時間。日本より16時間遅い)のものです。
10/20〜10/22の500は計71件で、69件が、所要時間59.9秒台から1分0秒台に揃っていました。 残りの2件は、1分を超えています。クライアントが接続を閉じたときに出る499は、全期間で2件だけです。 さらに、同じ日のログを時刻順に並べると、500と200が交互に並んでいました(ミニの時計、10/22の朝)。
| 時刻 | 状態 | 所要時間 |
|---|---|---|
| 08:11:30 | 500 | 59.999秒 |
| 08:11:50 | 200 | 19.9秒 |
| 08:12:31 | 200 | 40.9秒 |
| 08:12:49 | 200 | 18.4秒 |
| 08:13:49 | 500 | 59.996秒 |
| 08:14:15 | 200 | 25.7秒 |
| 08:15:15 | 500 | 59.99996秒 |
| 08:15:40 | 200 | 25.6秒 |
| 08:16:40 | 500 | 59.997秒 |
60秒で切れる重い要求のあとに、20〜40秒で返る軽い要求が来ます。重い要求が判定役の要求で、軽い要求が普通のやり取りだと考えれば、つじつまが合います。
60秒で、どこまで読めたか
llama.cppのログ(prompt processing, n_tokens = …, progress = …, t = …)には、60秒の時点で、どこまで読み込めていたかが残っています。60秒の時点で、まだ読み込み中だった要求は60件ありました。 直近12件では、読めていた量は5,120〜19,968トークンで、6件が12,288トークンでした。
そのうちの1件は、12,288トークンで進捗36%、59.73秒、毎秒205.7トークンでした。逆算すると、全体は約3.4万トークンです。このままの速さで読めば、終わるまでに100秒を超えます。
読み込み速度は、10/22の調査では毎秒205〜340トークンでした(直近12件の記録では、毎秒164〜333)。この速さで、約5.6千トークンの普通の要求は16〜27秒で終わります。ログにある、軽い要求の所要時間(18〜42秒)と、おおむね合います。
なぜ3.4万トークンも読むのか
自動判定は、Bashを実行するたびに、判定役へ要求を送ります。中身は、判定の規則と、これまでの会話です。
規則は、claude auto-mode defaults で表示できます。手元のClaude Code 2.1.308で数えると、82,941バイトでした。 英語中心の文章なので、バイト数を4で割った概算で、約2万トークンです。
公式ドキュメントには、判定役が見るのは「ユーザーのメッセージ、読み取り以外のツール呼び出し、CLAUDE.mdの内容」で、ツールの結果は取り除かれる、と書かれています。ここに会話が加わって、1回の要求は約3.4万トークンになりました。
なぜ判定役が、ミニのモデルなのか
公式ドキュメントによると、判定役への要求の宛先は、既定ではClaude Sonnet 5です。ただし、ANTHROPIC_DEFAULT_SONNET_MODEL を設定していると、その値になります(値がClaude Sonnet 5.5かOpus 5.5のときだけ、Sonnet 5)。
先ほどの起動スクリプトは、このSonnet用の環境変数を、ローカルのモデルにしていました。そのため、判定役への要求も、作業しているのと同じミニのモデルに届いていました。
Claude Codeの公式リポジトリのIssue(#84417)でも、メンテナーが同じ構造を認めています(2026年8月16日の回答)。2.1.210以降、判定役は、使っているエンドポイントに、claude-sonnet-5 というモデルIDで要求します。ローカルのモデルを使っていれば、そのローカルのモデルが判定役として動きます。回避策として挙がっていたのが、ANTHROPIC_DEFAULT_SONNET_MODEL をローカルのモデルIDにする方法です。
同じ回答には、Claude以外の自己ホストのモデルは、autoモードの対応構成ではない、とも書かれています。ミニの入口は、対応外の構成でした。 このIssueは、動きがないまま、9月13日に自動で閉じられています。

しかも、判定役の要求と、普通の要求とでは、先頭の3トークンしか共通していません(10/22の調査記録)。Qwen3.8は、再帰的な層を持つモデルなので、先頭が違うと、前の読み込みを使い回せません。ログには、次の行が18件ありました。
forcing full prompt re-processing due to lack of cache data (likely due to SWA or hybrid/recurrent memory, see …)
毎回、全文を最初から読み直しています。料理に例えると、同じ料理人が、鍋をかき混ぜながら、出す前の検分もしている状態です。検分の決まりごとが長い巻物なので、鍋の前に戻る時間がありません。
直したこと
起動スクリプトの最後を、次のようにしました。
exec claude --bare --permission-mode "${MINI_PERMISSION_MODE:-manual}" ...
manual は、claude --help の選択肢に出ます(acceptEdits / auto / bypassPermissions / manual / dontAsk / plan)。公式ドキュメントでは、設定値 default の別名で、シェルコマンドを実行する前に人へ確認するモードです。判定役を呼びません。
そのままでは、ミニへのトンネルを張るsshも毎回確認になるので、許可リストに、そのコマンド1本だけを足しました。
"Bash(ssh -f -N -o BatchMode=yes -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ConnectTimeout=5 -L 127.0.0.1:11435:127.0.0.1:11434 <ユーザー>@<ミニのIP>:*)"
任意のsshを通すのではなく、トンネルを張るこの形だけです。

使わなかった選択肢もあります。公式ドキュメントによると、Claude Code v2.1.302以降は、CLAUDE_CODE_AUTO_MODE_MODEL で、判定役のモデルIDだけを別にできます。ただし、判定役の要求も、ANTHROPIC_BASE_URL の向き先(ここではミニ)に届くことが、別のIssue(#78372)で報告されています。モデルIDを変えても、ミニ以外のモデルに届くとは限りません。試していないので、今回は使っていません。
直したあとも、続いていた
この記事を書く前に、ミニのログをもう一度見ました。ミニの時計で10/22の08:11〜08:22(日本の10/23の00:11〜00:22)に、同じ「約60秒の500」と「200」の交互が、6組ありました。 最後に処理中だった要求は、約2.4万トークンを毎秒約170で読んでいて、17,408トークンの時点で102.8秒、進捗73%でした。60秒には収まりません。
これが判定役の要求なのかは、確認できていません。変更の前に開いたウィンドウが、autoのままだった可能性があります(起動スクリプトの変更は、開き直さないと反映されません)。
もう1つ、詰まったことがあります。同じモデルに、読み込み速度を測る要求を直接送ったところ、4分以上返ってきませんでした。同じ時間帯にほかの要求が処理されていたので、その後ろで待たされた可能性があります(確認していません)。共有している相手に測定の要求を足すと、自分の測定が詰まるだけでなく、ほかの作業の邪魔になりえるので、途中で止めました。
まだ確かめていないこと
- 60秒の上限が、どの部品のものか。 ログは500で、クライアントが閉じたときの499とは別です。ただし、Claude Code側のタイムアウトが、サーバーに500を返させた可能性は消せていません。公式ドキュメントには、秒数の記載がありません
- 続いている500が、判定役の要求かどうか
- manualにしたあとで、500が減ったか。 反映後の測定は、まだです
- MLX版(gemma4)でも同じか。 集計はモデルを区別していません
- 判定役の要求が、ミニにどのモデル名で届いていたか。 公式ドキュメントに、
--debug-fileでclassifier_request_startedのmodel=を見る手順があります。実行していません
学んだこと
- 「一時障害」と出ても、同じ失敗が繰り返すなら、一時ではない。 まず数える。日別、状態別、所要時間別に分けると、60秒に揃っているという事実が出てきました
- 判定役の宛先は、環境変数で決まる。 モデル名の変数をすべてローカルに向けるなら、判定役もローカルに向いたと考える
- 時間の上限は、読み込み速度を掛けて、量に直す。 毎秒205トークンなら、60秒で約12,300トークン。それを超える入力は、構造的に通らない
- 共有している相手に、測定を足さない。 測るなら、ログを読む
関連する記事です。
- auto判定とローカルLLM — 判定役の宛先の決まり方、60秒で読める量の見積もり、ログでの見分け方、直し方
- OpenClawの切り分け — ミニのエージェントが動かないときの、層ごとの切り分け
- OpenClawが動かない5つの症状 無反応から暴走まで、原因と直し方 — ミニで踏んだ、別の5つの不具合
- 作っているもの — 開発日記の元になっているプロダクト