「VSCode から繋ぐ設定は?」で始まり、リリース中に 3 つの罠を踏み、直後に別の外部から声が届いた — v2.10.0、`coderouter vscode-init` を出荷した話
TL;DR: 93 日目、雑談ひとつから始まった。「VSCode から CodeRouter に繋ぐ設定は?」── 答えを書いていたら、これはCLIサブコマンド1個で吐き出せると気づいた。`coderouter vscode-init`、`.vscode/settings.json` に `terminal.integrated.env.*` をマージ書きする冪等な scaffolder。既存の `editor.fontSize` も、terminal env 内の `PATH` も温存する。stdlib のみ・依存ゼロ増・33 テスト全緑。`.envrc`(direnv)と Cline / Roo / Continue.dev 用のコピペスニペットまで自動出力。設計〜出荷〜タグ打ちまで半日。ただし途中で 3 つの罠を踏んだ。(1) Continue.dev の JSON スニペットに閉じ括弧が 1 個多く、コピペしたら構文エラー ── f-string と生文字列を混ぜて `}}` エスケープを取り違えた自分のバグ。`json.dumps` で組む形に直した。(2) ユーザー実機で llama-cpp-local が半々失敗、fallback が救ってはいたが `unhealthy` バッジ点灯 ── Launcher で 8086 起動、`providers.yaml` は 8085 期待、というポート不一致だった。3 通りの回避策と `cr-check` シェル関数を docs にまとめて再発防止。(3) CI で ruff が 4 件(RUF100 / I001 / F401 x2)── 「S105 (hardcoded password) 念のため無効化しとくか」の noqa が有効ルールでないため RUF100 で撃たれる、テストの未使用 import が2件。ruff --fix で解消、fixup コミットで push。そして出荷から数時間後、issue #77 が立った ── wackyx3 さんが、VSCode Copilot Chat の「その他のモデル」から CodeRouter に繋げないかと聞いてきた。試しに Ollama のプロファイルの baseAPI を `:8088` に向けた、が「石だった」。理由はプロトコル軸のズレ ── Copilot Chat の Ollama プロバイダは Ollama ネイティブ API を叩くので、Anthropic/OpenAI 互換しか話さない CodeRouter とはワイヤーが合わない。外部エージェント連携の要望が、初リリース当日にいきなり来た。v2.10.0 と firelzrd さんの 2 回目の PR(`ProviderConfig.timeout_s` の上限を 600s → 86400s へ)を rebase で同梱、CHANGELOG に `### Fixed` でクレジット。
あらすじ — 37 話目です
初コミットは 2026-04-19。本記事の作業日は 2026-07-20、初コミットから 93 日目です。前話(第 36 話)は昨日 07-19 の v2.9.4、92 日目に初めて外部から PR が届いた回。
第 1〜9 話 (v1.8 → v2.2):「動く」→「壊れない」→「壊れても自分で直る」
第 10〜21 話 (v2.3 → v2.6.0):Plugin SDK / Launcher / 上流の事故をフィルタで受ける
第 22〜26 話 (v2.7.0 → v2.7.6):AI レビュー軍団で 26,656 行を総ざらい / 空応答フォールバックの実測 / MTP 対応
第 27〜31 話 (v2.7.7 → v2.9.0):外部エージェントを backend に / agent_cli を plugin へ切り出し
第 32〜35 話 (〜v2.9.1):サブエージェント E2E / grok の空応答修正 / launcher にモデル自動スワップを自前実装
第 36 話 (v2.9.4):92 日目、初めて外部から PR が届いた ── firelzrd 氏、KV キャッシュ破壊の修正
第 37 話 (本記事・v2.10.0):「VSCode から繋ぐ設定は?」の一言から、CLI scaffolder を作って出荷、直後に別の外部から声
昨日の PR は「見えていなかったバグを直してもらった」。今回は「見えていた面倒を消しに行ったら、別の面倒が 3 つ立ち上がって、出荷直後にまた別の要望が飛んできた」── 通低音の「机上で動くはず → 実機が裏切る → 直す」が、リリースサイクルそのものに入り込んだ回です。
きっかけ — 「VSCode から繋ぐ設定は?」
雑談から始まった。「VSCode で CodeRouter を使うときの設定はどうすればいい」。答えを書き出そうとしたら、想像より長くなった。
Claude Code なら `ANTHROPIC_BASE_URL=http://localhost:8088` と `ANTHROPIC_AUTH_TOKEN=dummy` を、統合ターミナルにだけ効くように仕込む
でも `~/.zshrc` に書くとグローバルに漏れて claude.ai コネクタと競合する
ワークスペーススコープなら `.vscode/settings.json` の `terminal.integrated.env.osx` / `.linux` / `.windows` の 3 つ全部
direnv 派なら `.envrc` を書いて `direnv allow`
Cline / Roo Code / Kilo Code は自前の設定 UI から `Base URL: http://localhost:8088/v1` と `API Key: dummy`
Continue.dev は `~/.continue/config.json` の `models` 配列に追記
これを毎回説明するのも、ユーザーが毎回実行するのも面倒だ。scaffolder が要る。VSCode 拡張を書くのが順当かと思ったが、TypeScript プロジェクトを1本立てて Marketplace 公開して保守するのは重い。CLI サブコマンド 1 個で 8 割解決できると踏んだ ── `coderouter vscode-init`。方針を書き出して、実装に入った。
実装 — 冪等・コンフリクト検出・atomic write
設計の中心は 3 つ。
(1) 既存 `settings.json` を壊さない。 マージ書き。無関係な top-level キー(`editor.fontSize`、`python.testing.pytestEnabled`)はもちろん、`terminal.integrated.env.osx` 内の他 env(ユーザーの `PATH` 加工)も温存する。マネージ対象は `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` / `CODEROUTER_MODE` の 3 つだけ。
(2) 冪等。 同じ引数で再実行すれば `unchanged`、既存の異なる値と衝突したら書かずに `conflict` を報告して exit 2。`--force` で明示的に上書き。オンボーディングスクリプトに何度も呼ばれても壊れないようにした。
(3) atomic write。 tmp ファイル → `os.replace`。書き途中に落ちても既存の `settings.json` は無傷。ワークスペース設定は壊すと VSCode ターミナル全体が黙って死ぬので、この一点は絶対に譲れなかった。
`.envrc` は `--with-envrc` でオプトイン。Cline / Roo / Continue.dev 用のスニペットは書き込まずstdout にコピペブロックで出す。それぞれの拡張の設定スキーマは向こうの都合で変わるので、自動化するとメンテのしっぽが延々続く。CLI からは「テンプレを提示するだけ」に留めた。
`coderouter/vscode_init.py` 584 行、stdlib のみ。CLI 配線は既存の `stats` / `audit` / `replay` と同じ「薄い argparse + 別モジュールに本体」パターン。テストは 33 件、fresh workspace / merge / conflict / `--force` / `--dry-run` の byte-parity / `.envrc` / 不正 JSON / OS 3 キー全部書き込み / CLI 経由の exit code 伝搬まで。
`.vscode/` は `.gitignore` に入っているので、生成した `settings.json` は既定で git に含まれない。ダミー `ANTHROPIC_AUTH_TOKEN` すらリポジトリには漏れない。設計の副産物として気に入っている。
罠その 1 — 自分のコードから壊れた JSON が出た
出荷してすぐ、ユーザーが `coderouter vscode-init --dry-run` を叩いて出力を貼ってくれた。ぱっと見きれいだが、末尾の Continue.dev スニペットに違和感があった。
Continue.dev — add to ~/.continue/config.json:
{"models": [{"title":"CodeRouter","provider":"openai","model":"any-id",
"apiBase":"http://localhost:8088/v1","apiKey":"dummy"}}]}閉じ括弧が `}}]}`。1 個多い。開き 2 個に対して閉じ 3 個。コピペした瞬間パースエラーだ。
原因は自分のコードの雑さだった。ヒアドキュメントで書けばよかったものを、f-string と生文字列を混ぜて連結していた。
' {"models": [{"title":"CodeRouter","provider":"openai",' # 生文字列 → { は 1 個
'"model":"any-id",\n'
f' "apiBase":"http://localhost:{port}/v1",' # f-string
'"apiKey":"dummy"}}]}\n' # 生文字列 → }} は 2 個のまま生文字列では `}}` はただの 2 文字。f-string なら 1 個の `}` にエスケープされる。同じブロック内で 2 種類を混ぜたので、片方だけエスケープが効かず 1 個余った。
直し方は「もう二度と踏まない」ことを優先した。ヘルパー関数を切って `json.dumps(model_entry, indent=2)` で組む。JSON の妥当性が構造的に保証される。ついでに、複数行の indent 付きで表示して読みやすくした。
テストも足した ── `_render_continue_snippet(port)` の出力を `json.loads` でラウンドトリップして妥当性を確認、`--port 4000` で `apiBase` に反映されることも確認。33 → 33 のまま(既存のテスト 1 件を差し替え、新規 2 件追加、happy path 1 件を減らした)。手書き文字列連結に戻せば必ずこのテストが赤くなる。
罠その 2 — ユーザー実機で Launcher と `providers.yaml` のポートがズレた
vscode-init で Cline を CodeRouter に繋いだ実機のダッシュボードを、ユーザーが送ってくれた。動いてはいる。応答も返っている。だが右上に `● unhealthy` のバッジ、そして providers パネル。
llama-cpp-local att=4 ok%=50% failed=2 [llama-cpp-local] transport error: All co...
vllm-local att=2 ok%=100%fallback が救っている ── llama-cpp が 2 回コケて、次のプロバイダの vllm が 2 回とも受け止めていた。usage mix は local 100%、$0.0000。設計思想どおりの動きではあるが、なぜ半々失敗しているのか。
ユーザーの一言で分かった。「最初 launcher でポート番号を 8085 でなく 8086 にしてた。設定では 8085 でないとダメに書いていたため」。
構造的な問題だ。`providers.yaml` に `base_url: http://localhost:8085/v1` と手書きしている一方で、Launcher UI のポート欄には 8086 を入れて起動した。CodeRouter は起動時にはそのポートに backend が居ないことを知らない。実リクエストが飛んだ瞬間に `httpx.ConnectError` を全リトライで受け取り、`transport error: All connections failed` として上位に伝える。fallback があれば救われる、無ければ 502。
このハマりは docs に残すべきだと決めた。書いた分は 3 つ:
Launcher ガイドに「providers.yaml とのポート整合」節を新設。3 通りの回避策 ── (A) 従来のハードコード派、(B) v2.7.4 の Launcher 自動同期(`launcher-llamacpp-8085` のようにポートを provider 名に埋める)、(C) 折衷(ハードコード+ `option_profiles` にポート固定)
トラブルシューティング §1-7 に症状面 ── ダッシュボードの `unhealthy` バッジと半々失敗の見え方、`coderouter doctor --check-model` での即断コマンド
予防策として `cr-check` シェル関数のスニペット。作業開始前に叩けばポート不一致は秒で分かる
「これは Launcher UI 側で ⚠️ 警告を出せば構造的に防げる」という改善案も同時に浮かんだが、v2.10.0 のスコープからは外した。docs で残しておくのが今できる最速だ。
罠その 3 — CI で ruff が 4 件
commit → タグ → push、CI が回った。「テストだけ失敗」。中身は ruff。
RUF100 [*] Unused `noqa` directive (non-enabled: `S105`)
DEFAULT_TOKEN = "dummy" # noqa: S105 — intentional placeholder, not a secret`"dummy"` が hardcoded password 扱いで S105 に引っかかると想定して念のため `noqa: S105` を付けていた ── が、この repo は ruff の S105 ルールを有効にしていない。有効でないルールへの noqa は RUF100 で撃たれる。先回りが裏目。「有効ルールでない」ことを事前に確認せずに書いた自分のミス。
もう 2 件はテストの import が未使用(`FileOutcome` / `VSCodeInitResult` を書いたが対応するテストを結局書かなかった名残)と、それに起因する I001(block 未整列)。`ruff --fix` で自動修正、pytest 33/33 継続、fixup コミットで main に push。dev tooling only、runtime 影響ゼロ、pip install ユーザーには無影響なので v2.10.0 タグは force-move せず、単発の follow-up commit で決着させた。「タグは意味の単位、fixup は上に積む」── これは CodeRouter の履歴で守っているルールだ。
相乗り ── firelzrd さんの 2 回目の PR
出荷準備中に `git pull --rebase origin main` したら、コンフリクト無しで 1 コミット降ってきた。#76、`fix(config): raise ProviderConfig.timeout_s upper bound to 86400s (24h)`、著者は firelzrd。前話(第 36 話)で 92 日目に初めて外から PR が届いた、あの firelzrd さんの2 回目だ。
`ProviderConfig.timeout_s` は Pydantic の `le=600` で上限を 10 分に切っていた。長時間の推論(重い reasoning モデル、大 context のスイープ、バッチ処理)で 600 秒制限がバリデーションエラーを引き起こす、という具体的な指摘。デフォルトの 30s と下限 1s は不変、上限だけ 24h に引き上げ。1 ファイル 1 行の変更で、レビュー観点も少ない。
これも v2.10.0 に同梱すべきだと即断した。rebase で自然に含まれるので、あとは CHANGELOG に `### Fixed` セクションを 1 つ足して firelzrd さんをクレジットするだけ。「初回の PR が入って翌日にはもう 2 回目」── この速度は嬉しい誤算だった。
出荷直後 ── issue #77 が立った
v2.10.0 を PyPI と GitHub に上げて数時間後、issue #77 が立った。wackyx3 さんから、質問だ。
最近、vscode の copilot chat 機能は、外部言語モデルを追加できるようになったようで、
チャットの設定覧から、モデル選択→その他のモデルで、モデルの追加を選ぶと
著名な Anthropic、Google、OpenAI の他、Ollama などが選べるようです。
...
このように、CodeRouter も対応可能でしょうか?
(試しに、ollama の設定の baseAPI を coderouter の 8088 番ポートに切り替えたら、石はダメでした)
面白い展開だった。今日出したのは「VSCode の Claude Code / Cline / Continue に繋ぎやすくする」だったが、質問はさらにその隣 ── GitHub Copilot Chat の「その他のモデル」枠への相乗り。VSCode 側の受け皿が拡張ではなく本体機能として増えたなら、そこに CodeRouter を挿せないかは自然な発想だ。
ダメだった理由は明確だ。プロトコルの軸が違う。Copilot Chat の Ollama プロバイダは Ollama ネイティブ API(`/api/generate` `/api/chat` など)を叩く。一方 CodeRouter が話すのは Anthropic 互換 `/v1/messages` と OpenAI 互換 `/v1/chat/completions` の 2 種類。同じ「LLM の API」でもワイヤーが違うので、baseAPI を差し替えただけでは通らない。
一方で「Copilot Chat が OpenAI 互換プロバイダを追加できる」なら話は別で、その入口に `http://localhost:8088/v1` を渡せば理屈上つながる。ここは Copilot Chat 側の実装次第(ドキュメントを追う必要がある)。あるいは、CodeRouter 側に Ollama ネイティブ API 互換の入口を足すという設計上の選択もある。Ollama がデファクトの「ローカル LLM の共通の顔」になりつつあるなら、投資対効果は決して低くない。
現状は「即答」ではないが、issue #77 に方針を書いて留めた。v2.10.0 が出た当日に、次のリリースの種が外から届く── 92 日目の PR に続いて 93 日目の質問、外から声が届く頻度が急に上がった感触がある。
VSCodeとの接続ガイド
https://github.com/zephel01/CodeRouter/blob/main/docs/guides/vscode.md


まとめ
93 日目、雑談 1 つから始まった `vscode-init` を、実装から出荷まで半日で回した。中で踏んだ罠は 3 つ、全部自分の詰めの甘さだ ── JSON の閉じ括弧、想定しなかったユーザーの設定パターン、有効でないルールへの noqa。それぞれ「もう二度と踏まない仕掛け」を残した(`json.dumps` テスト、docs 3 箇所、`ruff --fix` の履歴)。罠を踏んだこと自体は消えないが、次に同じ罠を踏まない、それが個人 OSS で唯一積める資本だと思っている。
そして今回いちばん記憶に残るのは、リリース当日の 3 つの外来イベント ── firelzrd さんの 2 度目の PR、wackyx3 さんからの issue #77、そしてポート不一致を実機で踏んで教えてくれたユーザー。外から届く手が、こんなに早くリリースサイクルの中に入り込んでくるとは思っていなかった。設計の質問には設計で、要望には要望で応える。それだけを 1 サイクルの中でやると、機能追加より学びの密度が高い。
あなたなら、リリース直後に外から届いた要望を、次のリリースにどう組み込みますか。「今回のスコープ外」で流すのと、「即応で 1 リリース増やす」のと、境目はどこにありますか。よかったらコメントで聞かせてください。
CodeRouter は MIT ライセンスの OSS です: https://github.com/zephel01/CodeRouter
`pip install coderouter-cli` / `uvx coderouter-cli serve` で動きます。
v2.10.0 で紹介した `coderouter vscode-init` は、上記いずれかで導入後、プロジェクトフォルダで 1 回叩くだけです。issue #77 は https://github.com/zephel01/CodeRouter/issues/77 で追えます。
#CodeRouter #ローカルLLM #VSCode #ClaudeCode #Cline #Continue #OSS #個人開発 #CLIツール
いいなと思ったら応援しよう!
サーバー代とコーヒー代になります☕ 役に立ったら応援よろしくお願いします!