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

公開: 2026-10-23
開発日記Claude CodeOllamaローカル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時間遅い)のものです。

200(成功)60秒で切れた5002311833191415710/1710/2010/2110/2210/18と10/19は要求なし。日付はミニの時計(米西海岸時間)。
/v1/messages への要求の日別件数。60秒で切れた500は、10/17の1件から、10/22の57件まで増えた。

10/20〜10/22の500は計71件で、69件が、所要時間59.9秒台から1分0秒台に揃っていました。 残りの2件は、1分を超えています。クライアントが接続を閉じたときに出る499は、全期間で2件だけです。 さらに、同じ日のログを時刻順に並べると、500と200が交互に並んでいました(ミニの時計、10/22の朝)。

時刻状態所要時間
08:11:3050059.999秒
08:11:5020019.9秒
08:12:3120040.9秒
08:12:4920018.4秒
08:13:4950059.996秒
08:14:1520025.7秒
08:15:1550059.99996秒
08:15:4020025.6秒
08:16:4050059.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秒を超えます。

60秒で切れる0秒60秒120秒180秒通常の1通判定規則だけ判定役への要求約5.6千トークン約2万トークン(概算)約3.4万トークン16〜27秒59〜98秒100〜166秒
読み込みが毎秒340〜205トークンのときの所要時間(左端が速い場合、右端が遅い場合)。判定役への要求だけが、60秒の線を大きく越える。

読み込み速度は、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日に自動で閉じられています。

auto(既定の自動判定)Bashを実行したい判定役へ要求を送る判定役の宛先規則82,941バイト+会話=ミニの同じモデル60秒で500実行せず拒否モデル名の環境変数5つを、すべてミニに向けていたmanual(今回の入口)Bashを実行したい判定役を呼ばない許可リストに合えば実行合わなければ人に確認許可リストに足したのは、ミニへのトンネルを張るssh1本だけ。
autoでは、判定役の宛先が環境変数で決まる。全部をローカルに向けた入口では、判定のたびにミニの同じモデルが約3.4万トークンを読む。

同じ料理人が、鍋を混ぜながら、天井から垂れた長い巻物を一行ずつ確かめている厨房。棚には蓋をした皿が順番待ちで並ぶ

しかも、判定役の要求と、普通の要求とでは、先頭の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を通すのではなく、トンネルを張るこの形だけです。

大きな門が閂で閉じたまま、脇の小さな潜り戸から、小さな木札が1枚だけ差し出されている

使わなかった選択肢もあります。公式ドキュメントによると、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分以上返ってきませんでした。同じ時間帯にほかの要求が処理されていたので、その後ろで待たされた可能性があります(確認していません)。共有している相手に測定の要求を足すと、自分の測定が詰まるだけでなく、ほかの作業の邪魔になりえるので、途中で止めました。

まだ確かめていないこと

学んだこと

  1. 「一時障害」と出ても、同じ失敗が繰り返すなら、一時ではない。 まず数える。日別、状態別、所要時間別に分けると、60秒に揃っているという事実が出てきました
  2. 判定役の宛先は、環境変数で決まる。 モデル名の変数をすべてローカルに向けるなら、判定役もローカルに向いたと考える
  3. 時間の上限は、読み込み速度を掛けて、量に直す。 毎秒205トークンなら、60秒で約12,300トークン。それを超える入力は、構造的に通らない
  4. 共有している相手に、測定を足さない。 測るなら、ログを読む

関連する記事です。