tools-build 2025-12-17

ok this is kind of a long shot, but its really bugging us as we’re yet to see a clear answer. Our issue is that occasionally (but not always) when we build a lambda jar and run it via Localstack’s lambda support in CI, it fails to find the correct protocol implementations, resulting in an error like this:

No implementation of method: :get-conn of protocol: #'datomic-http.protocols.database-manager/DatabaseManagerProtocol found for class: datomic_http.system.client_database_manager.ClientDatabaseManager: java.lang.IllegalArgumentException
2025-12-17T21:40:02.708548301Z stdout F java.lang.IllegalArgumentException: No implementation of method: :get-conn of protocol: #'datomic-http.protocols.database-manager/DatabaseManagerProtocol found for class: datomic_http.system.client_database_manager.ClientDatabaseManager
We’ve seen this with several distinct defrecord implementations, and we’re sure our classes do actually implement the protocol, and we have proof of the same code working in uberjar. To build our lambda jars we’re using tools.build with clojure.tools.build.api/jar. One slight wrinkle is that our protocols are often in a different subproject which is linked via :local/root . Is there anything I could look into regarding tools.build that could be (non-determinically) missing the protocol implementation side?

Try making the user.clj be in a totally different source set and only included in a :dev alias

Alex Miller (Clojure team) 2026-01-20T21:15:07.548929Z

that was suggested above

do you have a user.clj ?

that kind of error is generally indicative of a namespace containing the protocol definition being loaded twice

yes there’s a user.clj

that is likely the issue, it is loading the namespaces before compilation also loads them, which does weird things

I have a :build alias in my top level deps.edn

:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.11"}
                 org.clj-commons/digest {:mvn/version "1.4.100"}

and there’s definitely user.clj code on cp when this is invoked

yeah, that is not good

Alex Miller (Clojure team) 2025-12-17T22:13:18.865349Z

Not sure that explains the non-determinism though. Using dynamic loading or requiring-resolve anywhere?

no requiring-resolve in our user code, I think some in dependent libs

this build namespace runs in ci to generate the jar, so I don’t need the user code per se, is there an easy way to opt out of loading those ns?

these are our own protocols btw

put user.clj in a different path, and only add it in a specific alias and don't use that alias when building

Alex Miller (Clojure team) 2025-12-17T22:15:10.930399Z

In some cases you can resolve this by explicitly compiling protocols first (I assume you are compiling)

yea ok, both of those I can try

yes compiling via b/compile-clj first

yeah, non-determinism, who knows, could be be anything (maybe some kind of weird flip flopping when state isn't properly cleared between builds)

that’s sorta what I was thinking too. crazy thing is the build is being checked out supposedly fresh each time in CI but I’ll double check for any cache points. Potential this is an unpack issue with localstack lambda also

we have plans to unify our protocols into a dedicated project anyway, that would make it easier to precompile too. thanks everyone

oh, if you are are compiling things separately that can also lead to this kind of thing

the other thing you might want to make sure of is that your defrecords are actually satisfying the protocol, and not the interface that backs the protocol

;; bad 

(require '[some-ns-with-protocol :as n])

(import 'n.p)

(defrecord R p (someMethod [this]))

;; good 

(require '[some-ns-with-protocol :as n])

(defrecord R n/p (someMethod [this]))

(ns datomic-http.system.client-database-manager
  (:require
   [clojure.tools.logging :as log]
   [com.stuartsierra.component :as component]
   [datomic-http.client :as dc]
   [datomic-http.protocols.database-manager :as dbp :refer [DatabaseManagerProtocol]]
   [datomic-http.protocols.healthcheck :refer [HealthcheckProtocol]]
   [datomic-http.utils :refer [try-bool]]))

(defrecord ClientDatabaseManager [server-url]

  component/Lifecycle

  (start [this]
    (log/info "setting up client database manager")
    (assoc this
           :connections (atom {})
           :client (dc/client {:server-url server-url})
           :board-shard-count 4))

  (stop [this]
    (log/info "tearing down client database manager")
    (reset! (:connections this) {}))

  DatabaseManagerProtocol

  (connections [this]
    @(:connections this))

  (get-conn [this db-name]
    (or (get @(:connections this) db-name)
        (as-> (dc/connect (:client this) {:db-name db-name}) $
          (swap! (:connections this) assoc db-name $)
          (get $ db-name))))

👍 1

referencing via :refer usually

so, we ended up filing an issue with Localstack and one current hypothesis is that there’s a partial unpack bug when dealing with lambda bundles. If we can confirm this it means there’s not an actual build issue here, which honestly makes more sense since we don’t see this in any other env