Claude Media
Claude CodeでClojureをREPL駆動で書く設定 — ソケットREPLとhooksで評価を回す

Claude CodeでClojureをREPL駆動で書く設定 — ソケットREPLとhooksで評価を回す

Clojureの開発をClaude Codeに任せるなら、ファイル編集のたびに起動し直さず、常駐REPLへ式を送って確かめさせる形が合います。ソケットREPL、CLAUDE.md、hooks、権限の設定例をまとめます。

Clojureを書くClaude Codeには、エディタのREPL連携がありません。その代わりに、JVMの起動オプションだけで開けるソケットREPLがあり、BashツールからそこへTCPで式を送れば、人間がエディタで押す「評価」と同じことをClaudeにさせられます。

この記事では、常駐REPLを立てる手順、Claudeに守らせる規約をCLAUDE.mdに書く方法、編集のたびに自動でファイルを読み直すhooks、そして評価を権限でどう扱うかを順に組み立てます。

REPL駆動をClaudeに回させる意味

ClojureのREPLは、動いているプログラムに対して式を1つずつ評価し、結果を見ながら作っていく環境です。ClojureのREPLガイドは、小さな作業を手で試し、少しずつ自動化していく進め方を利点に挙げています。デバッグでも、問題を再現し、症状を観察し、実験を重ねて原因を絞ります。

この進め方は、Claude Codeの作業ループとよく噛み合います。普通の言語では「ファイルを直す → テストを走らせる → 出力を読む」の1周ごとにプロセスを起動し直します。Clojureでは起動済みのJVMに関数定義だけを送り込めるので、1周が軽くなります。

ただし、素のClaude Codeは何もしなければ clojure -M -e のような使い捨ての起動に寄りがちです。起動のたびにライブラリを読み込み、def した値も消えます。常駐REPLを渡して「ここに評価しなさい」と伝えるのが、この記事の中心です。

方法状態の持ち越し向く場面
clojure -M -e '式' の使い捨て状態の持ち越しなし(毎回JVMごと起動)向く場面1回だけ式を確かめる、CIに近い確認
ソケットREPLへ送る状態の持ち越しあり(Varとロード済みの名前空間)向く場面関数を直しては試す反復、データを手元に持ったままの調査

-e は clojure.main の「式を評価して、nilでない値を表示する」オプションです。

手順1: ソケットREPLを常駐させる

ソケットサーバーはJVMのシステムプロパティ clojure.server.<名前> で起動します。値はednのマップで、port と accept が必須です。address を省くと、待ち受けはループバックに限られます。

Clojure CLIなら -J でJVMにオプションを渡します。リファレンスにある例は次のとおりです。

clj -J-Dclojure.server.repl="{:port 5555 :accept clojure.core.server/repl}"

この方法は、通常のターミナルREPLも同時に起動します。Claude Codeとは別のターミナルタブで動かしておくと、Claudeが送った式の副作用を、自分の目でも追えます。

接続の確認には、telnetやnetcatが使えます。式を1つ送ってみます。

printf '%s\n' '(+ 2 3)' | nc 127.0.0.1 5555

返ってくるのは、リファレンスのtelnet例と同じ形、つまり user=> のプロンプトに続く評価結果です。標準入力が閉じたあとの切断の挙動はncの実装によって違うため、接続が居残る場合は、使っているncのタイムアウトや入力終了のオプションを調べて足してください。

Claudeに渡すときは、この呼び出しを1つのスクリプトにしておくと扱いやすくなります。

#!/usr/bin/env bash
# bin/repl-eval  使い方: bin/repl-eval '(+ 2 3)'
printf '%s\n' "$1" | nc -w 5 127.0.0.1 5555

accept に指定した clojure.core.server/repl は、接続のたびに呼ばれます。接続をまたいで残るのは、def で作ったVarと、ロード済みの名前空間です。式の途中で作ったローカルな状態を次の呼び出しに期待しないでください。

手順2: CLAUDE.mdに「評価の作法」を書く

Claudeは、REPLがあることを知らなければ使いません。プロジェクトのCLAUDE.mdに、接続先と作法を書きます。REPLガイドから、Claudeの動作に直結する項目を拾った例です。

## REPL駆動の開発
 
- 開発中は localhost:5555 にソケットREPLが起動している。式の評価は
  `bin/repl-eval '<式>'` で行う。毎回 `clojure -M -e` で起動し直さない
- ファイルを編集したら、REPLでそのファイルを読み直す。
  読み直しはhooksが自動で行うので、結果が出ていなければ手動で
  `(load-file "<パス>")` を送る
- 試したい式は、ソースの `(comment ...)` ブロックに残す。
  ファイル読み込み時に評価されてはいけない副作用つきの呼び出しは、
  必ずこのブロックの中に置く
- 関数を別の関数へ渡すときは、値ではなく `#'my-fn` のようにVarを渡す。
  これで再定義がループ中の呼び出しにも反映される
- 派生したVar(他のVarの値から作るVar)を `def` しない。
  必要なら引数なしの関数にする
- REPLで確かめたことは、テストかコードに残す。REPLの履歴は残らない
- REPLは壊れたことを見つけるのが得意ではない。仕上げに必ずテストを走らせる

最後の3項目は、ガイドの注意書きに沿っています。REPLは一時的な媒体なので、学んだことは別の場所に置く。速さは進捗と同じではなく、計画なしにREPLへ向かうと気が散る。REPLは壊れたことの検知には向かない。

前と後

Varを通すと、再定義が実行中のループに届く

BEFORE

値を渡す書き方

(future (run! print-number-and-wait (range)))

ループが関数の値を握るので、print-number-and-wait を再定義しても動き続ける側は古いままです。

AFTER

Varを渡す書き方

(future (run! #'print-number-and-wait (range)))

Varは呼び出しのたびに現在の値を引き直します。再定義がそのまま反映されます。

CLAUDE.mdに規約を足すとき、長くなりすぎると指示が埋もれます。テストコマンドを書く場合の考え方はClaude CodeでVitestを回す — CLAUDE.mdに書くテストコマンドにある内容が、そのまま使えます。

手順3: 編集のたびにREPLへ読み込むhooks

Claudeがファイルを直したあと、人間のエディタなら「現在のファイルをREPLにロード」を押します。この操作はhooksで自動にできます。PostToolUse イベントを Edit|Write のマッチャーで受け、編集されたファイルの絶対パスをstdinのJSONから取り出して、load-file をREPLへ送ります。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/reload-clj.sh"
          }
        ]
      }
    ]
  }
}

スクリプト本体は次のとおりです。

#!/usr/bin/env bash
# .claude/hooks/reload-clj.sh
f=$(jq -r '.tool_input.file_path // empty')
case "$f" in *.clj|*.cljc) ;; *) exit 0 ;; esac
 
# REPLが起動していなければ何もしない
out=$(printf '(load-file "%s")\n' "$f" | nc -w 5 127.0.0.1 5555 2>&1) || exit 0
 
# 読み込みに失敗したら、エラーをClaudeに見せる(exit 2)
if printf '%s' "$out" | grep -qE 'Syntax error|Execution error|Exception'; then
  printf '%s\n' "$out" >&2
  exit 2
fi

exit 2の意味は、PostToolUse ではツールが実行済みである点が肝です。ブロックはできませんが、stderrの内容はClaudeに渡されます。コンパイルエラーや未解決のシンボルを、Claudeが次の一手で自分で読んで直せます。exit 0で終わったhookのstderrはClaudeに届かないので、失敗を知らせたいときだけexit 2にします。

grepの文言は、お使いのClojureのバージョンが出すエラー表記に合わせて調整してください。

load-file を選んだ理由は、エディタ連携の「現在のファイルをロード」と同じ働きだからです。名前空間の切り替えを送る手間も省けます。一方で、ファイルの先頭から全部を評価し直します。読み込みの二重実行で壊れる定義がある場合は、defonce を使います。ガイドも、1度だけ定義すべきVarには defonce を勧めています。状態を持つシステムのコードは、clojure.tools.namespace のような再読み込みの仕組みと合わせて考えてください。

hooks全般の組み立てはClaude CodeでLighthouse CIをhooksから回すが参考になります。

手順4: REPLへの評価をどう許可するか

bin/repl-eval を許可リストに入れておけば、毎回の確認を省けます。

{
  "permissions": {
    "allow": ["Bash(bin/repl-eval *)"]
  }
}

ここで1つ、設計として押さえておきたい点があります。Bashの許可ルールはコマンド文字列に対して働きます。スクリプトの引数として渡した式の中身までは見ません。REPLの式は、そのJVMの権限で任意のコードを実行できます。つまり bin/repl-eval を許可することは、ファイルの読み書きやコマンドの起動を含む、Clojureで書けることすべてを許可することと同じです。これはドキュメントの記述ではなく、仕組みからの筆者の推論です。

安全側に倒すなら、次のようにします。

  • ソケットREPLは開発用のJVMにだけ開く。既定どおり待ち受けはループバックにとどめ、address を外向きに広げない
  • 本番のJVMには開かない。ガイドが本番接続の手段として触れているのは、ソケットサーバー、nREPL、unreplといった手段で、そこでの運用は別の判断になる
  • 許可リストに入れるのは bin/repl-eval だけ。nc * のような広い許可は避ける

Bashルールは複合コマンドを分解して、各部分が個別に許可に合う必要があります。bin/repl-eval に引数を渡す形を保っておけば、printf | nc のパイプを許可リストに書かずに済みます。

落とし穴: sandbox内からREPLに届かない

ソケットREPLに接続できないとき、まず疑うのはsandboxです。sandboxingのページは、ローカルホストのサーバーに届かない症状を扱っています。

sandbox

localhostの扱い

  • macOS

    network.allowLocalBinding を true にすると、sandboxされたコマンドはlocalhostの任意のポートへ接続できます。ただし、同じlocalhostで待つ他のサービスにも届くようになります。

  • LinuxとWSL2

    sandbox内のlocalhostはコマンドごとに独立していて、ホスト側のサーバーには直接届きません。ホストのREPLを使うコマンドは、excludedCommands でsandbox外に出して実行します。その場合、ファイルシステムもネットワークも制限を受けません。

どちらの場合も、設定を緩めた分の代償があります。bin/repl-eval をsandbox外に出すと、REPLの式がsandboxの保護を通らなくなります。この点は手順4の推論とつながります。接続の症状の切り分けはClaude Codeのsandboxから繋がらない原因と回避にまとめています。

運用の目安: REPLに任せる範囲

REPLに寄せすぎないための線引きも、ガイドから引けます。

  • REPLを使うときは計画を持って入る。計画を頭に置けないなら書き出す
  • 小さな変更を重ねる進め方は、局所解にはまりやすい。設計の判断が要る場面では、いったんREPLから離れる
  • REPLの出力は消える。残すべきものはコード、テスト、comment ブロック、ドキュメントに移す
  • 自動で走るテストや静的解析、ホットリロードは、REPLと競合する手段ではなく併用するもの

REPLに寄せた最後の仕上げは、テストです。テストを先に書いてから進める流れはClaude Codeでテスト駆動開発(TDD)を回す手順で整理しています。ClojureでもREPLの評価結果は反復用、合否の判定はテストランナー、と分けておくと、Claudeに「終わった」を判断させる基準がぶれません。

よくある質問

nREPLではだめですか

使えます。ガイドも、エディタ連携の裏にあるものとしてnREPLに触れています。ただし、nREPLは専用のクライアントとプロトコルが必要です。ncで式を送れるソケットREPLは、Bashツールだけで完結するのが利点です。エディタと同じREPLに相乗りしたい場合など、nREPLを選ぶ理由が先にあるなら、そちらに合わせてください。

複数行の式は送れますか

printf '%s\n' に渡す引数にそのまま改行を含められます。引用符のエスケープが面倒なら、式を一時ファイルに書いて load-file で読ませる方法もあります。

ClojureScriptでも同じ設定ですか

ここで示したのはJVM上のClojureの手順です。ClojureScriptの評価系は別の仕組みなので、そのまま当てはめられません。

まとめ

Claude CodeでのClojure開発は、REPLの接続先をClaudeに教え、編集後の読み込みをhooksで自動にし、評価の許可範囲を意識して決めれば、REPL駆動の反復を任せられます。迷ったときの基準は、「試すのはREPL、確かめるのはテスト、残すのはコード」です。

この記事を共有:XはてブLinkedIn