一家に一台しか電話がなかった時代、長電話をしている人がいると、他の人は使えませんでした。番号が一つしかないので、順番を待つしかありません。急ぎの用があるときは受話器を置いてもらう交渉から始まります。
会社はこれを、代表番号の内側に内線番号を配ることで解きました。外から見える番号は一つでも、内側では机ごとに違う番号を持っている。同時に十本の通話が成立します。
番号が一つしかない場所には二人は入れない。だから番号を増やして配る。半世紀前に決着した話のはずでした。
ファイルは分かれているのに
AI に並列でコードを書かせるとき、作業フォルダを分けるところまでは素直に進みます。git には同じプロジェクトの二本目、三本目の作業フォルダを作る機能があって、それぞれが別の枝を開きます。ファイルは互いに影響しません。
そこまでやったあとで、両方で開発サーバーを立ち上げます。
Error: Port 3000 is already in use
ポートというのは localhost:3000 の 3000 の部分で、一つの番号は同時に一つのプログラムしか使えません。フォルダは分かれていても、ポートはパソコンに一組しかないので、二本目が入れません。
黒電話と同じ状態です。
データベースだともっと悪い
ポートは落ちるだけなので、まだ気づけます。
データベースの場合は落ちません。五本の作業フォルダが同じデータベースを見ていると、一本の作業がテーブルを作り替えた瞬間に、残りの四本がおかしくなります。しかもエラーは出ないので、なぜか動かないという形で現れます。
落ちるほうがまだ親切です。
自分専用に持つべきものを、宣言しておく
プロジェクトの設定に、自分専用に持つべきものを書いておけます。
{
"worktreeEnv": {
"PORT": { "kind": "port", "base": 3000 },
"API_PORT": { "kind": "port", "base": 4000 },
"DB_NAME": { "kind": "slug", "prefix": "myapp_" }
}
}
これを書いておくと、作業フォルダを作るたびに、そのフォルダ専用の番号と名前が予約されて、そのフォルダのターミナルに渡ります。
| フォルダ | PORT |
DB_NAME |
|---|---|---|
| 本体 | 3000 |
myapp_myapp |
fix-login |
3010 |
myapp_fix_login |
add-search |
3020 |
myapp_add_search |
三本とも同時に立ちます。
~/src/myapp PORT=3000 DB=myapp_myapp
└ yarn dev → localhost:3000 ○ 立つ
~/.mulmoterminal/worktrees/…/fix-login PORT=3010 DB=myapp_fix_login
└ yarn dev → localhost:3010 ○ 同時に立つ
~/.mulmoterminal/worktrees/…/add-search PORT=3020 DB=myapp_add_search
└ yarn dev → localhost:3020 ○ 同時に立つ
この仕組みを持っているのが MulmoTerminal です。ターミナルで動く AI を何枚ものマスに並べて見張るための画面で、一マスが一体の AI にあたります。無料で、オープンソースとして公開されています。
刻みが1ではなく10である理由
ここがこの仕組みで一番よく考えられているところです。なぜ 3001、3002 ではなく 3010、3020 なのか。
多くの開発サーバーは、そのポートが埋まっていたら次のポートへ勝手にずれる作りになっています。3000 を渡されたフォルダで、何かの理由で 3000 が埋まっていると、開発サーバーは自分で 3001 にずれます。
刻みが1だと、その 3001 は隣のフォルダの枠です。割り当ては正しいのに、実際の動作で衝突します。
10刻みにしておけば、数回ずれても隣の枠には届きません。道具の親切な挙動を前提に、あらかじめ枠を空けてあるわけです。こういう理由は、書いてなければ誰も再発見できません。
二本目の複製にも効く
副産物として効いてくる場所があります。
同じプロジェクトを acme と acme2 の二つのフォルダに取ってきている人は、作業フォルダを使っていなくても同じ問題を持っています。どちらも同じ base を宣言するので、あとのほうは次の空きである 3010 になります。
二つの複製も、作業フォルダ同士と同じように 3000 を取り合っていました。同じ仕組みで解決します。
番号ではなく名前も配れる
ポートだけでなく、名前も配れます。作業の名前から作った文字列に、指定した前置きを付けたものです。小文字と数字と下線で、63文字で切ります。
データベースの名前、SQLite のファイル名、コンテナの名前にそのまま使えます。63文字で切るのはデータベースの名前の上限に合わせたもので、境界がはっきりしています。
ただし、データベースを作るところまではやりません。他とぶつからない名前を渡すところまでで、その名前をどう使うかは各プロジェクトのスクリプトの仕事です。名前を配ると、データベースを作るは別の仕事で、後者はプロジェクトのほうが知っています。
一度予約した番号は動かない
一度予約した値は固定です。マスを開き直しても、アプリを再起動しても、パソコンを再起動しても、同じフォルダには同じ番号が返ります。
これは親切心ではありません。もし起動のたびにポートを測り直すと、そのフォルダ自身の開発サーバーが掴んでいるせいで使用中と判定されて、番号がずれます。自分自身から逃げることになるわけです。一回目は 3010、二回目は 3020、三回目は 3030。動いているサーバーがあるほど逃げるという壊れた挙動になります。
もう一つ理由があって、既存の会話に戻ったマスは環境変数を読み直しません。動いた値は、実際に走っているプロセスと食い違ったままになります。
測るのは最初に配るときの一回だけです。そのときにパソコン上の他のものが掴んでいるポートは飛ばされます。
衝突は起きるが、残らない
MulmoTerminal を二つ動かしていると、予約を読んで書くまでの一瞬に、両方が同じ値を選ぶことはあり得ます。
対処が面白いところです。鍵をかけて防ぐのではなく、書いた直後に検出します。予約の記録は追記しかしないファイルなので、両方が同じように読んで、先に載っているほうが勝ちという判定が一致します。負けたほうが解放して次の値を取ります。
衝突は起きえますが、残りません。鍵を持たずに、判定基準を一致させることで解決しています。
もらった値が、マスのヘッダーに出る
そのフォルダが何をもらったかは、ヘッダーに :3010 のように出ます。ポートはクリックすると開発サーバーが新しいタブで開き、変数の名前はホバーで出ます。
そのフォルダの全部のマスに出ます。AI のマスも、素のターミナルも、コマンドを走らせたマスも。とくに効くのはコマンドを走らせたマスで、yarn dev を動かしているのはそこだからです。
宣言していないプロジェクトでは何も描きません。有効にする操作は不要で、使わない人には何も見えません。
使ってみる
MulmoTerminal は、次の一行で立ち上がります。ターミナル(Windows なら PowerShell、Mac ならターミナル)を開いて、そのまま貼り付けます。
npx mulmoterminal@latest
ブラウザが開いて http://localhost:34567 が表示されれば成功です。インストールという作業はなく、合わなければ閉じるだけで終わります。止めるときは、打ったターミナルで Ctrl + C を押します。
動かない場合、足りないものは次の二つのどちらかです。
| 確かめ方 | 無ければ | |
|---|---|---|
| Node.js 22.12 以上 | node -v |
nodejs.org/ja/download の LTS。入れたらターミナルを閉じて開き直す |
| Claude Code | claude --version |
macOS は curl -fsSL https://claude.ai/install.sh | bash、Windows は irm https://claude.ai/install.ps1 | iex。そのあと claude でログイン |
Claude Code は、AI をターミナルから使うためのコマンドです。使うには Claude の有料プラン(Pro / Max / Team / Enterprise)か API のアカウントが必要で、無料プランには含まれていません。MulmoTerminal 自体は無料で、AI の使用量はその契約から引かれます。
この記事の話に入るには、開発サーバーを持っている git のプロジェクトを一つ用意します。そのプロジェクトの .mulmoterminal.json に、次を書きます。
{
"worktreeEnv": {
"PORT": { "kind": "port", "base": 3000 }
}
}
そのフォルダをマスで開いて、作業フォルダを二本切ります。起動フォームの下のほうに出る欄に名前を入れて、ボタンを押すだけです。
それぞれのマスのヘッダーに、:3000 と :3010 のように違う番号が出れば効いています。両方で yarn dev を走らせて、二つとも立てば確認は終わりです。
やめるときは、worktreeEnv の行を消します。予約の記録は残りますが、何も渡されなくなります。
まとめ
番号が一つしかない場所には二人は入れない、という問題は黒電話の時代からありました。会社はそれを、代表番号の内側に内線を配ることで解きました。
AI を並列で動かすと同じ問題が戻ってきます。ファイルは分けられてもポートは分かれないので、二本目の開発サーバーが落ちます。データベースの場合は落ちずに壊れます。
MulmoTerminal は、自分専用に持つべきものを宣言しておくと、作業フォルダごとに番号と名前を配ります。刻みが10なのは、開発サーバーが勝手にずれる先を隣の枠にしないためです。
