if anyone is interested, we have a more chopped down implementation of a claude code intergration with multiple nrepl servers in a monorepo.
it's a variation on the recommendations from the bhauman/clojure-mcp-light.
1 main difference is, that the discovery of running repls is done via the file,
and the staleness of port number mentioned in it is checked by netcat.
we are driving the cursive repl server from claude code via 2 scripts
1. eval
#!/bin/sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
NREPL_PORT_FILE="$SCRIPT_DIR/.nrepl-port"
NREPL_PORT="$(cat "$NREPL_PORT_FILE")"
# Assume $NREPL_PORT is opened on the same host as this script is executing on
# which wouldn't be the case, if we see the $NREPL_PORT_FILE over a network
# filesystem.
if nc -z 0.0.0.0 "$NREPL_PORT"; then
CLJ_EVAL_NREPL="$(which clj-nrepl-eval)"
(cd "$SCRIPT_DIR" && direnv exec . "$CLJ_EVAL_NREPL" --port "$NREPL_PORT" "$@")
else
# Remove presumably stale port file, to prevent AI seeing a stale port number
test -f "$NREPL_PORT_FILE" && rm "$NREPL_PORT_FILE"
cat <
2. start-nrepl-server:
#!/bin/sh
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "${SCRIPT_DIR}"
if [ -f deps.edn ]; then
exec direnv exec . clojure -M:test:dev:nrepl "$@"
else
echo "Missing ${SCRIPT_DIR}/deps.edn"; exit 1;
fi
which is introduced to claude via a /.claude/rules/clj-nrepl-eval.md :
---
origin: ""
---
# Clojure REPL Evaluation
This is a monorepo, containing multiple Clojure modules in subdirectories, each
containing an `eval` script, which you must use to run Clojure code, forms,
expressions, programs via nREPL for that module.
If there is no nREPL server process running for this project, the `eval` script
prints the shell command for starting it.
Ask the user whether they want to run that command and run in the background,
then retry the `eval` without any sleep!
`eval` accepts an optional `--timeout ` argument.
Always provide Clojure code via stdin!
Example:
shell
<module-dir>/eval --timeout 3000 <<'EOF'
(require 'some.name.space :reload)
EOF
Clojure code running in REPL should not read from stdin!
The REPL session persists between evaluations, so namespaces and state is
preserved.
Always use :reload when requiring namespaces to pick up source file changes.```this article helped to formulate this specific solution too: https://vlaaad.github.io/codex-in-the-repl
btw, this problem has re-appeared again on another machine, but we are still unclear on the reasons. no more energy to debug it though, so we fell back to copy-pasting the REPL command's code into the REPL window directly, since we needed to eval it only a few times. otherwise we are driving the cursive repl server from claude code
the root cause for these issues could be that we are trying to exclude the *.iml files from version control, because there is constant churn in them.
the ij project-root level iml file keeps changing its name in every new git-worktree, which in turn also modifies .idea/modules.xml.
since we don't commit these files anymore, on the 1st opening of worktree dir in intellij triggers a project discovery.
however, we do have some of the intellij and cursive configs under version control, like the .idea/repl-commands.xml, which might lead the initialization code in cursive towards some untested code-path.
I have had to upgrade from intellij 2025.2 to 2026.1.1. as is normally the case, my intellij cannot access the outside world and so cannot download leiningen. what I normally do is manually go and get leiningen jar manually and put it in the normal place ~/.lein/self-installs/leiningen-2.11.2-standalone.jar, then just make sure the intellij settings for lein match the version I have. then cursive does not try to download it. However, with the new upgrade, cursive is still trying to download lein (when I 'add as lein project' from intellij). lein jar is there, with the right permissions, and confirmed lein works from the cmd line. is there something else I can do to convince cursive not to try to download leiningen?
seems to be working as normal today. phew