Expected behavior
After connecting to a ClojureScript REPL and visiting a .cljs file in a buffer, the buffer should become connected (in sesman) to the REPL session, so that, e.g., M-. works.
Actual behavior
In some projects, this does not happen and the buffer remains unconnected, as indicated by [not connected] in the status line.
Steps to reproduce the problem
I have isolated a minimal project where I can observe this: https://github.com/nathell/cider-cljs-issue. Follow the README there to reproduce.
What I've been able to discover so far
- The gist of the problem is the macro-writing macro in
repro.macro. It looks like Orchard barfs on it in the process of connecting the buffer to the REPL session.
- At some point during connecting,
cider.nrepl.middleware.track-state/calculate-changed-project-state-response calls (vals (cljs-ana/all-ns cljs)) to get the project's namespaces. That includes the repro.macro ns description, which is returned like this:
{:rename-macros nil,
:renames {},
:meta
{:file "repro/core.cljs",
:line 1,
:column 5,
:end-line 1,
:end-column 15},
:ns-aliases
{cljs.loader shadow.loader,
clojure.pprint cljs.pprint,
clojure.spec.alpha cljs.spec.alpha},
:use-macros nil,
:excludes #{},
:name repro.core,
:reader-aliases {},
:imports nil,
:requires
{repro.macro repro.macro,
m repro.macro,
cljs.core cljs.core,
goog goog},
:seen #{:require},
:uses nil,
:defs
{with-ctx
{:protocol-inline nil,
:meta
{:macro true,
:arglists '([ctx & body]), ;; HERE
:doc
"Evaluates given body with given `*ctx*` value. See `*ctx*` for details.",
:top-fn
{:variadic? true,
:fixed-arity 1,
:max-fixed-arity 1,
:method-params ((ctx body)),
:arglists ([ctx & body]),
:arglists-meta (nil)},
:file nil},
:name repro.core/with-ctx,
:file "repro/core.cljs",
:top-fn
{:variadic? true,
:fixed-arity 1,
:max-fixed-arity 1,
:method-params ((ctx body)),
:arglists ([ctx & body]),
:arglists-meta (nil)},
:method-params ((ctx body)),
:protocol-impl nil,
:fixed-arity 1,
:arglists-meta (nil),
:column 1,
:variadic? true,
:methods [{:fixed-arity 1, :variadic? true, :tag seq}],
:line 4,
:macro true,
:ret-tag any,
:max-fixed-arity 1,
:fn-var false,
:arglists ([ctx & body]),
:doc
"Evaluates given body with given `*ctx*` value. See `*ctx*` for details."},
main
{:protocol-inline nil,
:meta
{:file "repro/core.cljs",
:line 6,
:column 7,
:end-line 6,
:end-column 11,
:arglists '([])},
:name repro.core/main,
:file "repro/core.cljs",
:end-column 11,
:method-params ([]),
:protocol-impl nil,
:arglists-meta (nil nil),
:column 1,
:variadic? false,
:line 6,
:ret-tag js,
:end-line 6,
:max-fixed-arity 0,
:fn-var true,
:arglists '([])}},
:require-macros
{cljs.core cljs.core, repro.macro repro.macro, m repro.macro},
:cljs.analyzer/constants
{:seen #{repro.core/*ctx* clojure.core/binding},
:order [clojure.core/binding repro.core/*ctx*]},
:flags {:require #{}},
:js-deps [],
:deps [goog cljs.core repro.macro]}
Note: I captured this with scope-capture by connecting to the Clojure/JVM REPL and judiciously peppering cider-nrepl with spy calls from there.
Note the line marked ;; HERE. The arglist there is quoted, i.e. it is actually (quote ([ctx & body])).
-
This ns definition then makes its way through cider.nrepl.middleware.track-state/initial-project-state, cider.nrepl.middleware.track-state/ns-state, orchard.indent/infer-style-indent, and orchard.indent/compute-style-indent.
-
The latter function is called with the second arg (arglists) being '([ctx & body]). This is then destructured to arglist being quote.
-
compute-style-indent then tries to call find-idx passing that symbol to it, thinking that it's a List. A ClassCastException ensues.
Here's a full stacktrace of that exception:
ERROR: Unhandled REPL handler exception processing message {:id 33, :op cider/get-state, :session 4371bd9e-f3cb-410d-b568-86dfafbbc239}
java.lang.ClassCastException: class clojure.lang.Symbol cannot be cast to class java.util.List (clojure.lang.Symbol is in unnamed module of loader 'app'; java.util.List is in module java.base of loader 'bootstrap')
at orchard.indent$find_idx.invokeStatic(indent.clj:107)
at orchard.indent$find_idx.invoke(indent.clj:106)
at orchard.indent$compute_style_indent.invokeStatic(indent.clj:120)
at orchard.indent$compute_style_indent.invoke(indent.clj:110)
at orchard.indent$infer_style_indent.invokeStatic(indent.clj:164)
at orchard.indent$infer_style_indent.invoke(indent.clj:151)
at cider.nrepl.middleware.track_state$ns_state$post_process__64337$fn__64338.invoke(track_state.clj:217)
at orchard.misc$update_vals$fn__26146.invoke(misc.clj:85)
at clojure.lang.PersistentHashMap$NodeSeq.kvreduce(PersistentHashMap.java:1309)
at clojure.lang.PersistentHashMap$BitmapIndexedNode.kvreduce(PersistentHashMap.java:804)
at clojure.lang.PersistentHashMap$ArrayNode.kvreduce(PersistentHashMap.java:468)
at clojure.lang.PersistentHashMap.kvreduce(PersistentHashMap.java:238)
at clojure.core$fn__8559.invokeStatic(core.clj:6987)
at clojure.core$fn__8559.invoke(core.clj:6967)
at clojure.core.protocols$fn__8287$G__8282__8296.invoke(protocols.clj:174)
at clojure.core$reduce_kv.invokeStatic(core.clj:6998)
at clojure.core$reduce_kv.invoke(core.clj:6989)
at orchard.misc$update_vals.invokeStatic(misc.clj:84)
at orchard.misc$update_vals.invoke(misc.clj:81)
at cider.nrepl.middleware.track_state$ns_state$post_process__64337.invoke(track_state.clj:216)
at cider.nrepl.middleware.track_state$ns_state.invokeStatic(track_state.clj:225)
at cider.nrepl.middleware.track_state$ns_state.invoke(track_state.clj:205)
at cider.nrepl.middleware.track_state$ns_state.invokeStatic(track_state.clj:232)
at cider.nrepl.middleware.track_state$ns_state.invoke(track_state.clj:205)
at cider.nrepl.middleware.track_state$merge_used_aliases$fn__64353$fn__64355.invoke(track_state.clj:275)
at clojure.lang.PersistentHashMap$NodeSeq.kvreduce(PersistentHashMap.java:1309)
at clojure.lang.PersistentHashMap$BitmapIndexedNode.kvreduce(PersistentHashMap.java:804)
at clojure.lang.PersistentHashMap.kvreduce(PersistentHashMap.java:238)
at clojure.core$fn__8559.invokeStatic(core.clj:6987)
at clojure.core$fn__8559.invoke(core.clj:6967)
at clojure.core.protocols$fn__8287$G__8282__8296.invoke(protocols.clj:174)
at clojure.core$reduce_kv.invokeStatic(core.clj:6998)
at clojure.core$reduce_kv.invoke(core.clj:6989)
at cider.nrepl.middleware.track_state$merge_used_aliases$fn__64353.invoke(track_state.clj:272)
at clojure.lang.PersistentHashMap$NodeSeq.kvreduce(PersistentHashMap.java:1309)
at clojure.lang.PersistentHashMap$BitmapIndexedNode.kvreduce(PersistentHashMap.java:804)
at clojure.lang.PersistentHashMap$ArrayNode.kvreduce(PersistentHashMap.java:468)
at clojure.lang.PersistentHashMap.kvreduce(PersistentHashMap.java:238)
at clojure.core$fn__8559.invokeStatic(core.clj:6987)
at clojure.core$fn__8559.invoke(core.clj:6967)
at clojure.core.protocols$fn__8287$G__8282__8296.invoke(protocols.clj:174)
at clojure.core$reduce_kv.invokeStatic(core.clj:6998)
at clojure.core$reduce_kv.invoke(core.clj:6989)
at cider.nrepl.middleware.track_state$merge_used_aliases.invokeStatic(track_state.clj:271)
at cider.nrepl.middleware.track_state$merge_used_aliases.invoke(track_state.clj:268)
at cider.nrepl.middleware.track_state$calculate_changed_project_state_response.invokeStatic(track_state.clj:313)
at cider.nrepl.middleware.track_state$calculate_changed_project_state_response.invoke(track_state.clj:292)
at cider.nrepl.middleware.track_state$handle_tracker.invokeStatic(track_state.clj:343)
at cider.nrepl.middleware.track_state$handle_tracker.invoke(track_state.clj:341)
at clojure.lang.Var.invoke(Var.java:390)
at cider.nrepl$wrap_tracker$fn__28486.invoke(nrepl.clj:1067)
at cider.nrepl$wrap_version$fn__28504.invoke(nrepl.clj:1095)
at cider.nrepl$wrap_xref$fn__28512.invoke(nrepl.clj:1105)
at shadow.cljs.devtools.server.nrepl$shadow_init$fn__24635.invoke(nrepl.clj:28)
at shadow.cljs.devtools.server.nrepl_impl$handle.invokeStatic(nrepl_impl.clj:334)
at shadow.cljs.devtools.server.nrepl_impl$handle.invoke(nrepl_impl.clj:225)
at shadow.cljs.devtools.server.nrepl$middleware$fn__24644.invoke(nrepl.clj:46)
at nrepl.middleware.print$wrap_print$fn__23169.invoke(print.clj:187)
at nrepl.middleware.session$session$fn__24414.invoke(session.clj:360)
at refactor_nrepl.middleware$wrap_refactor$fn__29390.invoke(middleware.clj:244)
at clojure.lang.AFn.applyToHelper(AFn.java:154)
at clojure.lang.AFn.applyTo(AFn.java:144)
at clojure.lang.AFunction$1.doInvoke(AFunction.java:33)
at clojure.lang.RestFn.invoke(RestFn.java:411)
at shadow.cljs.devtools.server.nrepl$start$fn__24676.invoke(nrepl.clj:148)
at nrepl.server$handle_STAR_.invokeStatic(server.clj:27)
at nrepl.server$handle_STAR_.invoke(server.clj:24)
at nrepl.server$handle$reify__24442.run(server.clj:47)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:545)
Further speculation and info
It is unclear to me whether the bug is in Orchard, cider-nrepl, ClojureScript, or CIDER itself. I've listed them in decreasing order of likelihood as reported by my gut feeling.
My actual codebase where this happens uses the taoensso libraries encore and telemere, of which the first one includes the macro-writing macro that triggers this.
I think this can be related to #3909, and also possibly #3914.
Environment & Version information
CIDER version information
;; CIDER 1.22.0-snapshot (package: 20260518.2116), nREPL 1.3.1
;; Clojure 1.12.4, Java 25.0.2
Lein / Clojure CLI version
Clojure CLI version 1.12.4.1582
shadow-cljs 3.4.11
Emacs version
GNU Emacs 29.3 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.41, cairo version 1.18.0) of 2024-04-01, modified by Debian
Operating system
Ubuntu MATE 24.04.4 LTS
JDK distribution
openjdk version "25.0.2" 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Ubuntu-124.04, mixed mode, sharing)
Expected behavior
After connecting to a ClojureScript REPL and visiting a .cljs file in a buffer, the buffer should become connected (in sesman) to the REPL session, so that, e.g.,
M-.works.Actual behavior
In some projects, this does not happen and the buffer remains unconnected, as indicated by
[not connected]in the status line.Steps to reproduce the problem
I have isolated a minimal project where I can observe this: https://github.com/nathell/cider-cljs-issue. Follow the README there to reproduce.
What I've been able to discover so far
repro.macro. It looks like Orchard barfs on it in the process of connecting the buffer to the REPL session.cider.nrepl.middleware.track-state/calculate-changed-project-state-responsecalls(vals (cljs-ana/all-ns cljs))to get the project's namespaces. That includes therepro.macrons description, which is returned like this:{:rename-macros nil, :renames {}, :meta {:file "repro/core.cljs", :line 1, :column 5, :end-line 1, :end-column 15}, :ns-aliases {cljs.loader shadow.loader, clojure.pprint cljs.pprint, clojure.spec.alpha cljs.spec.alpha}, :use-macros nil, :excludes #{}, :name repro.core, :reader-aliases {}, :imports nil, :requires {repro.macro repro.macro, m repro.macro, cljs.core cljs.core, goog goog}, :seen #{:require}, :uses nil, :defs {with-ctx {:protocol-inline nil, :meta {:macro true, :arglists '([ctx & body]), ;; HERE :doc "Evaluates given body with given `*ctx*` value. See `*ctx*` for details.", :top-fn {:variadic? true, :fixed-arity 1, :max-fixed-arity 1, :method-params ((ctx body)), :arglists ([ctx & body]), :arglists-meta (nil)}, :file nil}, :name repro.core/with-ctx, :file "repro/core.cljs", :top-fn {:variadic? true, :fixed-arity 1, :max-fixed-arity 1, :method-params ((ctx body)), :arglists ([ctx & body]), :arglists-meta (nil)}, :method-params ((ctx body)), :protocol-impl nil, :fixed-arity 1, :arglists-meta (nil), :column 1, :variadic? true, :methods [{:fixed-arity 1, :variadic? true, :tag seq}], :line 4, :macro true, :ret-tag any, :max-fixed-arity 1, :fn-var false, :arglists ([ctx & body]), :doc "Evaluates given body with given `*ctx*` value. See `*ctx*` for details."}, main {:protocol-inline nil, :meta {:file "repro/core.cljs", :line 6, :column 7, :end-line 6, :end-column 11, :arglists '([])}, :name repro.core/main, :file "repro/core.cljs", :end-column 11, :method-params ([]), :protocol-impl nil, :arglists-meta (nil nil), :column 1, :variadic? false, :line 6, :ret-tag js, :end-line 6, :max-fixed-arity 0, :fn-var true, :arglists '([])}}, :require-macros {cljs.core cljs.core, repro.macro repro.macro, m repro.macro}, :cljs.analyzer/constants {:seen #{repro.core/*ctx* clojure.core/binding}, :order [clojure.core/binding repro.core/*ctx*]}, :flags {:require #{}}, :js-deps [], :deps [goog cljs.core repro.macro]}Note: I captured this with scope-capture by connecting to the Clojure/JVM REPL and judiciously peppering cider-nrepl with
spycalls from there.Note the line marked
;; HERE. The arglist there is quoted, i.e. it is actually(quote ([ctx & body])).This ns definition then makes its way through
cider.nrepl.middleware.track-state/initial-project-state,cider.nrepl.middleware.track-state/ns-state,orchard.indent/infer-style-indent, andorchard.indent/compute-style-indent.The latter function is called with the second arg (
arglists) being'([ctx & body]). This is then destructured toarglistbeingquote.compute-style-indentthen tries to callfind-idxpassing that symbol to it, thinking that it's aList. A ClassCastException ensues.Here's a full stacktrace of that exception:
Further speculation and info
It is unclear to me whether the bug is in Orchard, cider-nrepl, ClojureScript, or CIDER itself. I've listed them in decreasing order of likelihood as reported by my gut feeling.
My actual codebase where this happens uses the taoensso libraries
encoreandtelemere, of which the first one includes the macro-writing macro that triggers this.I think this can be related to #3909, and also possibly #3914.
Environment & Version information
CIDER version information
Lein / Clojure CLI version
Clojure CLI version 1.12.4.1582
shadow-cljs 3.4.11
Emacs version
GNU Emacs 29.3 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.41, cairo version 1.18.0) of 2024-04-01, modified by Debian
Operating system
Ubuntu MATE 24.04.4 LTS
JDK distribution
openjdk version "25.0.2" 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Ubuntu-124.04, mixed mode, sharing)