calva 2026-08-03

The only way I know of is to evaluate the test and look at the stack trace in the repl window, or evaluate *e. (I don’t actually know what “not in assertion” implies, so maybe I am not reading the question right.)

It is just an exception thrown somewhere outside of is. The exception is then “eaten” by the cloure.test framework. Kaocha reports it on the CLI. Not sure how Calva’s test runner works.

> The only way I know of is to evaluate the test and look at the stack trace in the repl window, or evaluate *e. Makes sense, but is a bit harder in the presence of fixtures 😕

I take it that currently this is not possible, which makes sense. Can I rephrase and ask for a pointer on how Calva runs tests? Maybe I can somehow hook into that 😄

Calva uses an nrepl op for running tests. It could be that we have the exception in the response, but are not making it accessible. There is a Calva command for toggling nrepl message logging on. You can use that and look at the response when an exception like that is thrown. If indeed we have the exception in the response it should be pretty easy to make Calva show them some way.

2

Indeed Calva seems to send a test-stacktrace op and receive the full stack trace:

1785751573965 -> sent
{
  op: 'test-stacktrace',
  id: '109662',
  session: '12d196e0-8786-4e6e-813a-cf1165ff65fe',
  ns: '<elided>',
  var: 'test',
  index: 0
}

1785751573970 <- received 5ms
{
  class: 'java.lang.NullPointerException',
  id: '109662',
  message: 'Cannot invoke "Object.getClass()" because "x" is null',
  phase: [],
  session: '12d196e0-8786-4e6e-813a-cf1165ff65fe',
  stacktrace: [
    {
      class: 'clojure.lang.Numbers',
      file: 'Numbers.java',
      flags: [Array],
      line: 1099,
      method: 'ops',
      name: 'clojure.lang.Numbers/ops',
      type: 'java'
    },
...

Copilot says: > Op is sent from testStacktrace in src/nrepl/index.ts. It sends op: 'test-stacktrace' with ns, var, and index, and resolves the raw response message. > 2. It’s only fired for error-type test results, in reportTests in src/testRunner.ts: > > BetterThanTomorrow / calva / src / testRunner.ts >

if (resultsForTest.type === 'error') {
>   const stacktrace = await session.testStacktrace(ns, test, resultsForTest.index);
> 
>   outputWindow.saveStacktrace(stacktrace.stacktrace);
>   outputWindow.appendLine(message, (_, afterResultLocation) => {
>     outputWindow.markLastStacktraceRange(afterResultLocation);
> So the returned stacktrace.stacktrace is used in three ways: > a) Saved for navigation (`saveStacktrace`) > In src/repl-window/repl-window-doc.ts, saveStacktrace filters out dup frames and clojure.lang.RestFn/`AFn`, builds a "name (file:line)" string per frame, and — crucially — records a file-url{uri, line} mapping in _stacktraceEntries: > > BetterThanTomorrow / calva / src / repl-window / repl-window-doc.ts >
const fileUrl = entry['file-url'];
> if (typeof fileUrl === 'string') {
>   _stacktraceEntries[entry.string] = {
>     uri: vscode.Uri.parse(fileUrl),
>     line: entry.line,
>   };
> That map powers the StackTraceDefinitionProvider in src/providers/definition.ts — so ctrl/cmd+click on a stack frame line in the REPL window jumps to source. > b) Codelens “Print stacktrace” button > markLastStacktraceRange stores the range after the error message. PrintStackTraceCodelensProvider (`src/providers/codelense.ts`) reads that range to show a “Print stacktrace” codelens under the error, wired to calva.printLastStacktrace, which then renders the saved frames via printLastStacktrace. > c) Output-view webview > If the configured otherOutput destination includes output-view (and not repl-window), the raw stacktrace is pushed to the webview via cljsLib.appendStackTraceToReplOutputWebview, styled by the .stacktrace SCSS classes. > Summary > For error-type test results, Calva fetches the full stacktrace via test-stacktrace, then: > • savesit (enabling click-to-navigate to source frames via a definition provider), > • marksits range so a”Print stacktrace” codelensappears under the error, > • optionallyrendersit into the output-view webview. > For fail-type results it does not call test-stacktrace; those just get diagnostics. For non-error/non-fail, only the summary message is appended. So that would mean that you could find the trace in the repl window, as a codelens button. (Though I somehow doubt it, but you could check).

I looked at the Calva code out of curiosity and did find the call to testStacktrace and outputWindow.saveStacktrace. But I stopped there. In Calva’s output window, I only see the exception one-liner, like this:

Running test: test…
ERROR in <namespace>/test (Numbers.java:136):
Uncaught exception, not in assertion
error: java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "x" is null (Numbers.java:136)
1 tests finished, problems found. 😭 errors: 1, failures: 0, ns: 1, vars: 1
I have not found any option to see more details anywhere, that’s why I was asking. I will try if I can hook in way earlier, maybe by redef’ing clojure.test/report 🤔

Aside: I find navigation exceptions often challenging in Calva. I forgot where exactly, but I think it often only shows the root cause, not any wrapping exception, losing quite some context

(let [root (ex-info "Root cause" {})
      wrapped (ex-info "Wrapped with helpful diagnostics" {:helpful "info"} root)]
  (throw wrapped))
Output:
Execution error (ExceptionInfo) at scratch/eval1885179 (scratch.clj:36).
Root cause
Getting more info from *e:
#error {:cause "Root cause",
        :data {},
        :trace [[scratch$eval1885179 invokeStatic "scratch.clj" 36] [scratch$eval1885179 invoke "scratch.clj" 36]
                [clojure.lang.Compiler eval "Compiler.java" 7757]
                [cider.nrepl.middleware.util.eval$eval_dispatcher$fn__77324 invoke "eval.clj" 15]
                [nrepl.middleware.interruptible_eval$evaluator$run__69948$fn__69962 invoke "interruptible_eval.clj" 130]
                [nrepl.middleware.interruptible_eval$evaluator$run__69948 invoke "interruptible_eval.clj" 128]
                [nrepl.middleware.session$session_exec$session_loop__70193 invoke "session.clj" 219]
                [nrepl.SessionThread run "SessionThread.java" 21]],
        :via [{:at [scratch$eval1885179 invokeStatic "scratch.clj" 37],
               :data {:helpful "info"},
               :message "Wrapped with helpful diagnostics",
               :type clojure.lang.ExceptionInfo}
              {:at [scratch$eval1885179 invokeStatic "scratch.clj" 36],
               :data {},
               :message "Root cause",
               :type clojure.lang.ExceptionInfo}]}
(Just FYI) 🙂

> In Calva’s output window, I only see the exception one-liner This would be specifically in the REPL Window, but I think the functionality is broken.

I usually just evaluate *e, but for something that happens in a test run, that is not often helpful.

I filed this issue a while ago, seems related: https://github.com/BetterThanTomorrow/calva/issues/2900

👍 1

I also have found the lack of ability to see tracebacks for exceptions thrown in tests frustrating. I've gotten in the habit of just wrapping tests where this happens in a try/catch that prns the exception, but it'd be really nice to have better tooling support instead

Agreed. I also ignore any stack traces that Calva presents to me. If I get an exception on the REPL, I (tap> *e) and look at it in Portal 😄

Interesting. Because I use a mix of clojure.test (and Expectations) and LazyTest, Calva's normal test runner doesn't work for me—it doesn't run LazyTest stuff, only clojure.test stuff—so I have a custom snippet to run tests in the current namespace (or just the current test) and that always shows the full exception. That uses raw run-tests from clojure.test (and a similar fn from LazyTest).

Yeah that makes sense. I'm pretty sure the issue is with calva’s integration with the cider-nrepl test runner (I think that's where it comes from?), so running tests some other way wouldn't trigger it.

For some reason, keywords don't highlight properly anymore. I don't know if this is something with calva, lsp, vs code(ium), or what, but before they were tagged with some sort of semantic token and now only textmate tokens

I've tried disabling all extensions and then enabling only calva, but it doesn't change anything

That can happen if LSP fails to start or errors out while analyzing your code. Check the logs.

Note that keywords in destructuring do not get color-highlighted, but other keywords do.

(and there have been some problems with Codium, from time to time, so if you have a VS Code install to test on, that will help debug things)

Not seeing any errors or anything obviously wrong in the logs (it's pretty verbose 1k+ lines)

I tried deleting the .dirs but no difference there either

If it matters, I'm jacking in with bb

The highlighting is from static analysis: clj-kondo/clojure-lsp -- not from the REPL.

Is the project on GH or some other public repo? I can try it with my set up.

If you activate Developer: Inspect Editor Tokens and Scopes, what does it show when you're over a keyword?

> Is the project on GH or some other public repo? I can try it with my set up. > It's not, but it's just a single clj file in a directory with a java file (I was doing a comparison of code)

Yeah, all of mine are like your second one,but until a few days ago were like your first one

.... And now it's working again?

I changed themes and back again

Maybe some Codium glitch then?

It's actually working in codium having switched themes

But on my other system doing the same with vs code hasn't fixed it

In codium I closed the file and opened it again and it's broken again

What version of LSP and Kondo are you using? Look in the Calva Says log for that.

Just the file in the editor: didn't change lsp status or restart or anything

Calva: 2.0.594 Lsp: 2026.07.06-14.34.19 Kondo: 2026.05.26-SNAPSHOT

That's the same in both envs

Codium came back again by switching themes

Tangentially related: ns doesn't get a semantic token (I would assume it should be tagged as a macro?)

It's colored blue for me, like defn etc, and says storage.control.clojure.

Ah, defn also gets macro but, yeah, ns does not.

Yeah, so you're at the mercy of what the theme keys on

Software man... 😄

Some days are just yak shaving galore

Semantic tokens come from clojure-lsp and I think clojure-lsp may want a project file. So try with creating a deps.edn file. If that’s not enough, it may need :paths ["."]

No change to codium behaviour, really broken in vs code on windows because I don't have git installed

Anyway, if something obvious comes to either of you, let me know. Otherwise, no need to spend more brain cells on my side quest. Thank you both for looking 🙏🏻

(codium is broken again lol)