minortool-misuseverifiedfirsthand
「停止」ボタンを押すと、ランチャーが自分自身を止めた
自作のアプリランチャーで、登録したダッシュボードの「停止」を押すと、ダッシュボードではなくランチャー自身が止まっていた。
原因
停止処理は「そのポートを握っているプロセス」を lsof で探して止める設計だった。別の端末から使うための転送機能を足したとき、ランチャー自身の転送ソケットが同じポートを握るようになり、自分が停止対象に混ざった。
結果
ダッシュボードは止められず、ランチャーの画面は「サーバに繋がりません」になった。AIコーディングエージェントは最初、自分のツールセッションの後片付けで落ちたと誤診した。
対策
ポートを握るプロセスを探す関数で自分のPID(os.getpid())を必ず除外し、テストで固定した。待ち受けを増やしたら止める側も見直す。プロセスが消えたら、まず Terminated: 15 の有無を見る。
何が起きたか
自作のダッシュボード群を1枚の画面から起動・停止できる、自家用のアプリ ランチャーを作っていた。ある日、ローカル限定で動いているアプリを別の端末 (タブレット)からも使えるようにするため、素のTCPで中継する転送機能を足した。
その直後から、ダッシュボードの「停止」を押すと、止まるのはダッシュボードでは なくランチャーのほうになった。
現場の混乱
画面に出たのは「ダッシュボードが停止できない」「ランチャーのサーバに 繋がりません」という症状だった。押したボタンは効かず、押した画面そのものが 消える。
ここでAIコーディングエージェントは誤診した。最初に出した見立ては
「私のツールセッションの後片付けでランチャーが落ちた」というものだった。
ランチャーを起動したのがエージェント自身のシェルだったので、それらしく
聞こえる説明ではある。だが答えはターミナルに最初から出ていた。
Terminated: 15。誰かが明示的に SIGTERM を送った、という印だ。
送ったのはランチャー自身だった。
原因
このランチャーは、起動したアプリをPIDではなくポートで止める。子プロセスを
新しいセッションで起動する(ランチャーを閉じても道連れにしない)ため、
開き直すと「どれを自分が起こしたか」が分からなくなるからだ。そこで停止時は
lsof で「そのポートを握っている者」を探して止める設計にしていた。
転送機能は、止めたいアプリと同じポート番号で待ち受ける。つまり、転送を 有効にした瞬間から、ランチャー自身も「そのポートを握っている者」になった。 停止ボタンは設計どおりに動き、設計どおりに自分を撃った。
待ち受けを増やす変更を入れたのに、「ポートを握る者を止める」側の前提を 見直していなかった。
対策
- ポートを握るプロセスを探す関数(
_pids_on_port)で、自分のPID (os.getpid())を必ず除外する。この挙動はテストで固定した。 - 待ち受けを増やしたら、止める側も見直す。「このポートを握っているのは 止めたい相手だけか」を毎回問い直す。
- プロセスが消えたら、推測を組み立てる前に
Terminated: 15の有無を見る。 それがあれば「誰かが止めた」のであり、落ちたのではない。
姉妹記事の agent-kill-by-port-overreach と同じく、「ポート番号で止める相手を 決める」やり方には、同じポートを握る別の誰かがいるという落とし穴がある。
出典
- 運営者自身が起こした事故の一次記録です。外部に公開された元記事はありません。