tools-build

2025-12-17T22:02:09.640929Z

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?

emccue 2026-01-20T20:50:59.265839Z

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

2025-12-17T22:08:36.740739Z

do you have a user.clj ?

2025-12-17T22:09:10.774419Z

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

2025-12-17T22:10:04.352829Z

yes there’s a user.clj

2025-12-17T22:10:46.926419Z

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

2025-12-17T22:12:20.764089Z

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

2025-12-17T22:12:22.132059Z

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

2025-12-17T22:12:41.257259Z

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

2025-12-17T22:13:08.084849Z

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?

2025-12-17T22:13:43.293169Z

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

2025-12-17T22:14:27.191109Z

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?

2025-12-17T22:14:39.575049Z

these are our own protocols btw

2025-12-17T22:15:04.554339Z

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)

2025-12-17T22:15:25.753419Z

yea ok, both of those I can try

2025-12-17T22:15:40.426359Z

yes compiling via b/compile-clj first

2025-12-17T22:19:52.069449Z

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

2025-12-17T22:20:55.052929Z

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

2025-12-17T22:21:28.847839Z

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

2025-12-17T22:22:12.994929Z

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

2025-12-17T22:24:43.273429Z

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

2025-12-17T22:25:31.669549Z

;; 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]))

2025-12-17T22:27:28.493709Z

(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
2025-12-17T22:27:55.614179Z

referencing via :refer usually

2026-01-21T15:05:02.412759Z

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