「プレビュー環境で確認をお願い」 「はい、確認しました。プレビューサーバーを起動しました」
AIエージェントとの協業は快適です。彼らは文句も言わずに何度でもビルドし、確認してくれます。 しかし、ふとタスクマネージャーを見ると、そこには地獄のような光景が広がっていました。
Node.js のプロセスが、10個も20個も起動したまま放置されていたのです。
親切心が招く「ポート枯渇」
原因は、Astro(および多くの現代的なフレームワーク)が持つ「開発者への親切設計」にありました。 通常、Webサーバーを起動する際にポート(例えば4321)が既に使われていると、エラーで終了するのが古典的な挙動です。しかしAstroは気を利かせてくれます。
「おっと、4321は埋まってるね。じゃあ自動的に 4322 で立ち上げるよ!」
人間が開発している分には便利な機能ですが、状態管理が苦手なAIエージェントがこれを扱うと、「前のプロセスを殺し忘れたまま、新しいポートで次々とサーバーを立ち上げ続ける」 というゾンビ増殖が発生します。
設定ファイルでも止まらない
当初、package.json で --port 4321 を指定したり、設定で strictPort: true を試みたりしました。
しかし、Astroの preview コマンド(静的ファイルサーバー)はこの設定を一部無視し、頑なに「空いているポートを探す」という親切な挙動を続けました。フレームワークの優しさは、時には残酷です。
Pythonで「殺して奪う」
フレームワークの設定で止められないなら、フレームワークを呼ぶ前に物理的に解決するしかありません。
「ポート4321を使っている奴がいるなら、そいつを殺してから自分が座る」 という、極めて野蛮かつ合理的なラッパースクリプト (strict_preview.py) を作成しました。
def kill_process_on_port(port):
"""ポートを占有しているプロセスを特定してKillする"""
# netstat でPIDを特定し、taskkill で強制終了
# (Windows環境の実装例)
subprocess.run(f"taskkill /F /PID {pid}", shell=True)
if __name__ == "__main__":
# 先住者を排除
kill_process_on_port(4321)
# 焼け野原になったポート4321で悠々と起動
subprocess.run(f"npx astro preview --port 4321", shell=True)
そして package.json を書き換えます。
"scripts": {
"preview": "uv run scripts/strict_preview.py"
}
結論:優しさはコードで殺す
これでようやくゾンビは死滅しました。
pnpm preview を叩けば、裏で古くなったサーバーが自動的に始末され、常に最新のサーバーがポート4321で立ち上がります。
「空いてるところを適当に使っていいよ」というブラックボックスな優しさよりも、「邪魔なやつはどけ」という明快な力技。これこそが、自動化された運用環境における唯一の正解でした。