devcontainerとホスト間のUnixソケットがmacOSでは通らないのでsocatのTCPリレーで通す

March 28, 2026

前回前々回に続きdevcontainerの話。

ClaudeCode の hooks 機能で、エージェントの状態(thinking, notification, done など)を tmux のウィンドウ名やスタイルに反映するscriptを自作してる。ホスト上では問題なく動くんだがmacOS の Docker環境で devcontainer 内から ClaudeCode を実行すると hooks が silent no-op になり、tmux ステータスが一切更新されなくなっていた。

macOS の Docker 環境では、bind mount した unixdomainソケットにコンテナ内から接続できないらしい。VirtioFSのファイル共有はソケットは見せるがkernelレベルのソケット通信はVM境界を越えさせないようだった。

$ ls -la /private/tmp/tmux-501/default
srwxrwx--- 1 ubuntu ubuntu 0 Mar 26 18:39 default   # ファイルは見える

$ tmux display-message -p "#{window_id}"
no server running on /private/tmp/tmux-501/default     # 接続は失敗

ファイルとしては見えるのにソケット通信が通らない。WSL(ubuntu)ではこの問題は発生しない(VM境界がなく、直接ソケットアクセスが通るって話)。

解決策

ホスト側でTCPリレー

unixdomainソケットが VM境界を越えられないということなので、ホスト側で socat で TCP リスナーを立て、hook の実行をリレーしつつホストに委譲。

コンテナ内で tmux コマンドを実行するのではなく、コンテナからはアクションとペイン ID だけを TCP で送信し、ホスト側の socat がそれを受けて hook スクリプトをホスト上で直接実行する。

ホスト側(devcontainer.jsonのinitializeCommandで起動):

SOCK=${TMUX%%,*}
command -v socat >/dev/null 2>&1 && [ -S "$SOCK" ] && {
  pkill -f 'socat.*TCP-LISTEN:2489' 2>/dev/null
  socat TCP-LISTEN:2489,bind=127.0.0.1,reuseaddr,fork \
    SYSTEM:'read action pane; TMUX_PANE=$pane bash ~/.claude/hooks/tmux-window-claude-status.sh $action' &
} || true

socat の SYSTEM アドレスにより、TCP 接続ごとに hook をホスト上で fork 実行する。read action pane で TCP から送られた 1 行を分割し、TMUX_PANE を設定してから hook を呼ぶ。

hook スクリプト側でフォールバック

コンテナ内の bash /dev/tcp 疑似デバイスで TCP を直接送信するため、コンテナ側に socat は不要。

command -v tmux &>/dev/null || exit 0

action="$1"
pane="$TMUX_PANE"

# Devcontainer内(=ソケット到達不可)ならTCPリレーへフォールバック
if ! tmux display-message -p '' 2>/dev/null; then
  [ "$DEVCONTAINER" = "true" ] && \
    { echo "$action $pane" >/dev/tcp/host.docker.internal/2489; } 2>/dev/null
  exit 0
fi

まとめ

環境 直接ソケット 動作
ホスト (macOS/WSL) 接続可 従来通り直接 tmux 操作
devcontainer on WSL 接続可 unixdomainソケットをmountし直接使用
devcontainer on macOS 接続不可 TCP リレーでホストに委譲

おわり

ファイルは見えるのにソケットが通らないという問題を、ubuntuかmacOSかという問題に間違って当てはめてしまい特定に時間がかかった。。

socat TCP リレーは力技感あるがコンテナ側の変更が最小で済むのはよい、軽量化を諦めなくて済む。hookはソケットが繋がるならそのまま使う/繋がらないなら TCP で投げる、というシンプルな分岐だけでいける。

コードはこちら

参考

#llm #docker #tmux #devcontainer #claude-code #sre #socat #macos