Claude Code を PTY で包むブラウザ端末(mulmoterminal)で、ターミナルのヘッダーに「そのセッションの直近プロンプト」を出しています。ところが Claude で /clear すると、会話はリセットされるのにヘッダーは /clear 前のプロンプトのまま固まる。次に何か入力しても更新されない、という報告でした。
結論から言うと、Claude Code は /clear(と /compact)で自分の session_id を新しい UUID に振り直すのが原因でした。こちらは「起動時に --session-id で指定した id」でセッションを追っているので、/clear 後のフック(UserPromptSubmit / Stop / …)は全部新しい idに飛び、旧 id を見ているクライアントには一切届かない——ヘッダーも activity も、放っておけばツール履歴もズレます。
診断:SessionStart を出してみる
SessionStart フックを登録してログを出したら一発でした。
[hook] SessionStart source=startup id=02670f5f-… livePty=true
[hook] UserPromptSubmit for 02670f5f-…
[hook] SessionStart source=clear id=74ca515c-… livePty=false ← /clear で id が変わった
[hook] UserPromptSubmit for 74ca515c-… ← 以降のフックは新 id へ
source=clear の SessionStart が別の UUIDで飛んでいて、しかもそれは「生きている PTY ではない(livePty=false)」= こちらが知らない id だと分かります。/compact も同様に新 id を発番します。
なぜ payload の session_id では相関できないか
フックのペイロード(stdin の JSON)に入っているのは Claude の現在の session_id だけ。/clear 後は新 id です。こちら側の安定 id(PTY とクライアントが追っている、起動時に決めた id)は payload に含まれません。しかもペイロードは Claude が送ってくるものなので、こちらから足せません。だから payload の session_id をキーに状態を持つと、/clear をまたいだ瞬間に相関が切れます。
直し方:安定 id をフックの header に埋める
我々の安定 id を注入できる唯一の場所は「フックのコマンドそのもの」です。curl のヘッダーに、spawn 時点の安定 id を焼き込みます。
function hookSettingsJson(host: string, sessionId: string) {
const cmd =
`curl -s -X POST http://${host}:${PORT}/api/hook ` +
`-H 'content-type: application/json' -H 'x-mt-session: ${sessionId}' -d @- >/dev/null 2>&1`;
// …UserPromptSubmit / Stop / SessionStart / *ToolUse をこの cmd で登録…
}
受け側は、payload の session_id より x-mt-session を優先します。
const mtHeader = req.headers["x-mt-session"];
const mt = typeof mtHeader === "string" && SESSION_ID_RE.test(mtHeader) ? mtHeader : null;
const sessionId = mt ?? body.session_id; // Claude の内部 id が変わっても、常に起動時の安定 id に紐づく
これで /clear で Claude の内部 id が変わっても、フックは常に「起動時の安定 id」に相関します。ついでに、SessionStart の source: "clear" を拾ってヘッダーのプロンプトを空にし、次の入力で更新されるようにしました。
if (event === "SessionStart" && body.source === "clear") clearHeaderPrompt(sessionId);
URL クエリ(?mt=)でも同じことはできますが、相関 id は「リソースの一部」ではなくメタデータなので header の方が素直です。ちなみに同アプリの GUI MCP も、セッション id を MCP の URL に spawn 時点で焼き込む同じ発想で相関を取っています。
コピペする前に知っておくと良いこと
- payload の
session_idを信じない。長寿命セッションに/clearや/compactを挟むツールなら、CLI が内部 id を振り直す前提で「自分が発番した安定 id」で相関するのが安全です。header 名は任意ですが、SESSION_ID_REなどで形式を検証してから使うこと。 - 永続 tmux の罠。mulmoterminal は Claude を永続 tmux セッションで動かすため、node サーバを再起動しても Claude 本体は再起動されず、
--settings(フック設定)は更新されません。フックの変更を試すには新規セッションを立てる必要があります(reattach では古い設定のまま)。ここを忘れると「フックが全然飛んでこない」と誤診しがちです(今回それで一度ハマりました)。
リセット系コマンドは id を作り直すと疑う
/clear や /compact のように会話をリセットする操作で、CLI が内部の session_id を振り直すことは十分あり得ます。フックで状態を追うなら「相手が送ってくる id」ではなく「自分が発番した id」で紐づける——これを最初から効かせておくと、この手の「リセットした瞬間に表示が固まる」を回避できます。