When a spec fails a 'keys*' spec, the 'in' path refers to the path after conforming. This is different from other specs (including 'cat' specs), where the 'in' path indicates the path in the original (pre-conformed) data.
rlwrap clj -Srepro -Sdeps '{:deps {org.clojure/spec.alpha {:mvn/version "0.2.176"}}}'
Clojure 1.9.0
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (s/def ::point1 (s/cat :x int? :y int?))
user/point1
user=> (s/def ::x int?)
:user/x
user=> (s/def ::y int?)
:user/y
user=> (s/def ::point2 (s/keys* :req-un [::x ::y]))
:user/point2
user=> ;; for `cat` spec, `in` will refer to the position before conformance
user=> (s/explain ::point1 [0 nil])
nil - failed: int? in: [1] at: [:y] spec: :user/point1
nil
user=> ;; but for `keys*` spec, `in` will refer to position AFTER conformance
user=> (s/explain ::point2 [:x 0 :y nil])
nil - failed: int? in: [:y] at: [:y] spec: :user/y
nil
user=>
This is confusing for users looking at the default error message, since their original data doesn't have a ':y' key in the example. Furthermore, this makes it difficult for third-party libs to display the bad value in context.