Claude Codeのttyをexpectでハックする -  TCP制御とログ監視で実現する柔軟な自動化基盤

## はじめに
devcontainer上でClaude Codeを使っていて、こんな経験はありませんか？

### よくある問題
- 長時間のタスク実行中、Claude Codeから入力を求めてきたことに気づかず作業が止まっていた
- 複数のタスクを並列で実行しており、どのタスクが入力待ちになっているか分からない
- Claude Codeへの入力がtty依存になっているので不便だ

### 解決したい課題
これを課題としてまとめると

1. **プロセス間通信の必要性**
    Claude Codeの端末内容を他プロセスから取得して通知などの処理を行わせたい

1. **汎用的な入力方法の確立**
    TTYに依存しない、より柔軟な入力方法が欲しい

### 今回の解決策
今回はClaude Codeの対話型インタフェースを維持しながら端末内容をログに出力し、TCP経由で制御するスクリプトを作成したので紹介します。

> **Note**: Claude Code自体にMCPが用意されていますが、今回は汎用的かつ簡単に処理を差し込みたかったため、スクリプトでの解決を選択しました。

## 「CLAUDE.mdで対処できるのでは？」という疑問について
### タスク完了
確かに、タスクが完了した場合はCLAUDE.mdで以下のような指示が可能です：

```markdown
タスク完了後notify.shを必ず実行すること
```

この指示によって結構いい感じに処理してくれます。

### タスク途中の入力要求
タスク途中の入力要求に関してはあまり有効ではありません。

これはLLMが一つ先の出力結果でユーザー入力が必要になると認識できていなければ、
入力要求を行う手前の出力で指示されたアクションを実行できないためです。

簡単に言うとLLMに対して未来の状態を基に直近の出力結果を決定させるということです。

少なくとも現時点では自己回帰型のLLMでは難しい処理であると認識しています。
（dLLMであれば話は変わってくるかもしれません）

## スクリプト概要
expectコマンドで処理を実現しています。
処理内容は以下の通り

```mermaid
graph TB
    Start([開始]) --> A["Claude Codeを疑似端末で起動"]
    A --> B["制御コマンド受付用<br/>TCPソケットサーバー起動"]
    B --> C["以下の処理を並列実行"]
    
    C --> D["Claude Codeの端末内容を/tmp/claude-code-terminal.logに記録"]
    C --> E["TCPソケットサーバーに送られたコマンドを基にClaude Codeの端末に入力を渡す"]
    C --> F["interactで通常の対話的セッションを維持"]
    
    style A fill:#e1f5fe
    style B fill:#fff3e0
    style C fill:#f3e5f5
```

## スクリプト
https://github.com/sk-guritech/claude-code-bridge
```bash:claude-code-bridge.exp
#!/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起動
    ```bash
    $ ./claude-code-bridge.exp
    # または
    $ expect claude-code-bridge.exp
    ```
- 端末内容監視
    ```bash
    $ tail -f /tmp/claude-code-terminal.log
    ```
- コマンド送信
    ```bash
    # テキストを入力して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に考えてもらったのですが、
絶対自分では思いつかないような訴求力の高いタイトルですね。