はじめに
devcontainer上でClaude Codeを使っていて、こんな経験はありませんか?
よくある問題
- 長時間のタスク実行中、Claude Codeから入力を求めてきたことに気づかず作業が止まっていた
- 複数のタスクを並列で実行しており、どのタスクが入力待ちになっているか分からない
- Claude Codeへの入力がtty依存になっているので不便だ
解決したい課題
これを課題としてまとめると
-
プロセス間通信の必要性
Claude Codeの端末内容を他プロセスから取得して通知などの処理を行わせたい -
汎用的な入力方法の確立
TTYに依存しない、より柔軟な入力方法が欲しい
今回の解決策
今回はClaude Codeの対話型インタフェースを維持しながら端末内容をログに出力し、TCP経由で制御するスクリプトを作成したので紹介します。
Note: Claude Code自体にMCPが用意されていますが、今回は汎用的かつ簡単に処理を差し込みたかったため、スクリプトでの解決を選択しました。
「CLAUDE.mdで対処できるのでは?」という疑問について
タスク完了
確かに、タスクが完了した場合はCLAUDE.mdで以下のような指示が可能です:
タスク完了後notify.shを必ず実行すること
この指示によって結構いい感じに処理してくれます。
タスク途中の入力要求
タスク途中の入力要求に関してはあまり有効ではありません。
これはLLMが一つ先の出力結果でユーザー入力が必要になると認識できていなければ、
入力要求を行う手前の出力で指示されたアクションを実行できないためです。
簡単に言うとLLMに対して未来の状態を基に直近の出力結果を決定させるということです。
少なくとも現時点では自己回帰型のLLMでは難しい処理であると認識しています。
(dLLMであれば話は変わってくるかもしれません)
スクリプト概要
expectコマンドで処理を実現しています。
処理内容は以下の通り
スクリプト
#!/usr/bin/expect -f
# Copyright (c) 2025 Sakaguchi Ryo sakaguchi@sk-techfirm.com
# Released under the MIT license
# https://opensource.org/licenses/MIT
# Usage: ./claude-code-bridge.exp [port_number]
# Example: ./claude-code-bridge.exp 8080
#
# Command examples:
# echo "send hello" > /dev/tcp/localhost/9999 # Input "hello" and send Enter key
# echo "enter" > /dev/tcp/localhost/9999 # Send Enter key only
# echo "up" > /dev/tcp/localhost/9999 # Up arrow key
# echo "down" > /dev/tcp/localhost/9999 # Down arrow key
# Default settings
set port 9999
set output_file "/tmp/claude-code-terminal.log"
# Argument processing
if {$argc > 0} {
set port [lindex $argv 0]
}
# Launch claude
spawn claude
set timeout -1
# Log file configuration
log_file -noappend $output_file
# TCP Socket server
socket -server accept_connection $port
proc accept_connection {sock addr port} {
fconfigure $sock -blocking 0 -buffering line
fileevent $sock readable [list handle_socket_input $sock]
}
proc handle_socket_input {sock} {
if {[catch {gets $sock line} result]} {
close $sock
return
}
if {$result >= 0 && $line != ""} {
if {[string match "send *" $line]} {
set cmd [string range $line 5 end]
send -- $cmd
sleep 0.01
send -- "\r"
} elseif {$line == "enter"} {
send -- "\r"
} elseif {$line == "up"} {
send -- "\033\[A"
} elseif {$line == "down"} {
send -- "\033\[B"
}
}
}
interact
利用方法
- Claude Code起動
$ ./claude-code-bridge.exp # または $ expect claude-code-bridge.exp
- 端末内容監視
$ tail -f /tmp/claude-code-terminal.log
- コマンド送信
# テキストを入力してEnterキーを送信 echo "send hello world" > /dev/tcp/localhost/9999 # Enterキーのみを送信 echo "enter" > /dev/tcp/localhost/9999 # 上矢印キー echo "up" > /dev/tcp/localhost/9999 # 下矢印キー echo "down" > /dev/tcp/localhost/9999
応用
以下の応用方法が考えられます
-
/tmp/claude-code-terminal.log
に対してマッチング処理を行い処理を差し込む- 通知
- git stashなどの退避処理
- 自動コマンド送信でのタスク割り込み
-
任意の方法でClaude CodeをTCP経由で制御
- 外出先でスマホなどからClaude Codeへの返答を行う
- Claude Codeの用意しているMCPでは困難な部分を制御する
上記の例はかなり簡単な例です。
本スクリプトによってかなり汎用的な入出力を使えるようなったため、
やろうと思えば物理空間上の現象を組み込むといったことも可能になるかもしれません。
余談
タイトルをClaudeに考えてもらったのですが、
絶対自分では思いつかないような訴求力の高いタイトルですね。