babashka

2026-03-10T13:59:57.917429Z

Hi @borkdude - I'm playing with the cool TUI stuff you recently integrated, and I noticed that if I ^C while doing charm stuff, it typically has hidden my cursor. So this led me to try to add a shutdown hook, which the docs say should run even in the event of a ^C interrupt - but when I use charm, my shutdown hook doesn't get called. Here is a small reproduction case:

#!/usr/bin/env bb

(require '[babashka.deps])

(babashka.deps/add-deps
 '{:deps {io.github.TimoKramer/charm.clj {:git/sha "a6657a8567758907d0b2d24cc1529de2b39c2640"}}})

(require '[charm.core :as charm])

(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))

(charm/run {:init {:count 0}
            :update (fn [state _] [state nil])
            :view (fn [state] (str "Count: " (:count state)))})

borkdude 2026-03-10T14:03:22.203169Z

Try to reproduce it using Jline only

2026-03-10T14:11:59.953699Z

Yes, it looks like just this is sufficient to cause it to happen:

(import '[org.jline.terminal TerminalBuilder])

(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))

(-> (TerminalBuilder/builder)
    (.build))

(println "Interrupt in the next 5s ...")

(Thread/sleep 5000)
If you ^C, the hook doesn't run, but upon normal exit (ie, when the sleep expires) it does.

2026-03-10T14:15:53.176609Z

Anyway, presumably not a babashka issue - just surprising to me that this is even possible.

borkdude 2026-03-10T14:20:20.815399Z

This works for me.

$ bb /tmp/dude.clj
Interrupt in the next 5s ...
^C
$
Try it in a fresh terminal. The terminal may be funky because of some stuff you did to it earlier

borkdude 2026-03-10T14:20:34.678809Z

this is your last example

2026-03-10T14:27:27.272439Z

Nope, still doesn't work for me - I tried in a new ghostty window, and also a new native terminal window.

2026-03-10T14:27:39.296409Z

That's interesting though - maybe a platform issue on the mac? Hmmm.

2026-03-10T14:28:54.052039Z

Thank you for trying it.

borkdude 2026-03-10T14:29:01.588839Z

I'm on a mac as well. iTerm2

borkdude 2026-03-10T14:29:34.223739Z

mac aarch64 (m4)

2026-03-10T14:30:00.924409Z

Lemme download iTerm2 really quick to try ...

2026-03-10T14:31:08.873849Z

Nope, still no hook. In any case, this means it must be on my side somehow (?) -- I'll let you know if I happen to figure it out.

borkdude 2026-03-10T14:32:42.787729Z

ah wait, you mean, the shutdown hook doesn't fire? I thought the problem was: ctrl-c doesn't exit the program.

2026-03-10T14:33:13.312899Z

No, the issue is that the shutdown hook doesn't fire.

borkdude 2026-03-10T14:33:30.091259Z

ah yes, I can reproduce that

borkdude 2026-03-10T14:33:39.347919Z

does it also happen on JVM Clojure?

2026-03-10T14:34:04.460599Z

Basically, charm messes up my terminal - it hides my cursor - so I was just trying to manually do something to emit a sequence to show my cursor again when the program exits ...

2026-03-10T14:34:28.186429Z

Haven't tried on JVM yet.

borkdude 2026-03-10T14:36:01.260149Z

JVM clojure same problem:

$  clojure -Sdeps '{:deps {org.jline/jline-terminal {:mvn/version "3.30.6"}}}' -M /tmp/dude.clj
Interrupt in the next 5s ...
^C

2026-03-10T14:36:28.443529Z

You're faster than me! I was only halfway there ... heh.

2026-03-10T14:37:07.683579Z

I'm sort of shocked that that is possible. Dang.

borkdude 2026-03-10T14:40:41.580089Z

I let Claude take a look at jline3's code:

Found it. Look at line 92-99:

  When signalHandler == SignalHandler.SIG_DFL (the default, set in AbstractTerminal constructor at line 87), jline calls Signals.registerDefault(signal.name()) for all signals including INT.

  Signals.registerDefault (line 112-121 of Signals.java) registers SIG_DFL — the default OS signal handler — which for SIGINT means immediately terminate the process without running shutdown hooks. That's Runtime.halt() behavior rather than System.exit().

  So the problem is: jline replaces the JVM's default SIGINT handler (which calls System.exit and runs shutdown hooks) with the OS-level SIG_DFL (which just kills the process). User-registered shutdown hooks never get a chance to run.

  The fix would be for jline to either:
  1. Not register SIG_DFL for INT (leave the JVM's default handler in place)
  2. Register a handler that calls System.exit() instead of using SIG_DFL

🔥 1
2026-03-10T14:42:40.917069Z

Very interesting ... and surprising to me that this is in jline.

borkdude 2026-03-10T14:42:48.050519Z

This should work:

(import '[org.jline.terminal TerminalBuilder])

(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))

(-> (TerminalBuilder/builder)
    (.nativeSignals false)
    (.build))

(println "Interrupt in the next 5s ...")

(Thread/sleep 5000)

🙏 1
borkdude 2026-03-10T14:42:57.407909Z

(.nativeSignals false)

2026-03-10T14:43:51.210639Z

Indeed it does work - but that's something that charm would have to do to be useful, unless I'm mistaken.

2026-03-10T14:44:07.735699Z

I really should start reaching for AI (as you did) when researching something like this.

2026-03-10T14:44:12.577739Z

I just don't have the reflex for it yet.

borkdude 2026-03-10T14:44:13.047739Z

yep. discuss with @timok

borkdude 2026-03-10T14:44:27.962229Z

no problem, this is useful to know for all bb users

2026-03-10T14:46:17.306979Z

It would be a good change for him to incorporate probably, because he has a finally block where he tries to restore terminal defaults when the program finishes, but it won't run if it gets interrupted like this ... which explains my cursor always being hidden by charm.

borkdude 2026-03-10T14:46:34.310549Z

👍

2026-04-02T11:21:35.196979Z

Sorry for the delay in responding @timok - I'll take a look soon, but thanks for doing this and for letting me know.

timo 2026-03-27T15:47:13.022529Z

I solved it now with looping the ctrl+c to the message channel so you as a consumer of the lib can interact as you want with the signal. hope this works for you

timo 2026-03-11T11:22:24.047579Z

cool, thanks for investigating... @pmooser in case you want to contribute I am happy to take a PR

2026-03-10T14:02:47.501429Z

It suppose it must be a charm issue ...

2026-03-10T14:46:47.663759Z

On a side note, it's cool that babashka conf filled up, although I dragged my butt for too long - next year maybe !

❤️ 2