diff --git a/deps.edn b/deps.edn
index 4157a856d55e704373da5dc9c9b68e9be42d29fc..6de96d28a1a825d43841902bd1d5510c8d83dd7e 100644
--- a/deps.edn
+++ b/deps.edn
@@ -230,7 +230,7 @@
     djblue/portal                {:mvn/version "0.51.0"}                    ; ui for inspecting values
     hashp/hashp                  {:mvn/version "0.2.2"}                     ; debugging/spying utility
     io.github.camsaul/humane-are {:mvn/version "1.0.2"}
-    io.github.metabase/hawk      {:sha "46835a0b808e86894f598472351c08d81aeb1910"}
+    io.github.metabase/hawk      {:sha "59cba703e757bd94c0cc698dbf5338bf36276991"}
     jonase/eastwood              {:mvn/version "1.4.2"                      ; inspects namespaces and reports possible problems using tools.analyzer
                                   :exclusions
                                   [org.ow2.asm/asm-all]}
@@ -351,7 +351,7 @@
     cljs-bean/cljs-bean                {:mvn/version "1.9.0"}
     com.lambdaisland/glogi             {:mvn/version "1.3.169"}
     io.github.metabase/hawk            {:git/url "https://github.com/metabase/hawk"
-                                        :git/sha "539eefaa31a43d52d7c9b5731f471bb6742e7131"}
+                                        :git/sha "59cba703e757bd94c0cc698dbf5338bf36276991"}
     org.clojars.mmb90/cljs-cache       {:mvn/version "0.1.4"}
     ;; Forcibly targeting a newer release than ships by default with shadow-cljs 2.20.20.
     ;; It fixes an issue where TypeScript can't parse the `cljs_env.js` output file.
diff --git a/modules/drivers/redshift/test/metabase/test/data/redshift.clj b/modules/drivers/redshift/test/metabase/test/data/redshift.clj
index 4bb9f45febfae01999e5ae1d193ddcf302b0956b..55f438f929bb0f7006b69a5cb8a01e6c262f80b6 100644
--- a/modules/drivers/redshift/test/metabase/test/data/redshift.clj
+++ b/modules/drivers/redshift/test/metabase/test/data/redshift.clj
@@ -174,6 +174,22 @@
      (delete-old-schemas! conn)
      (create-session-schema! conn))))
 
+(defn- delete-session-schema!
+  "Delete our session schema when the test suite has finished running (CLI only)."
+  [^java.sql.Connection conn]
+  (with-open [stmt (.createStatement conn)]
+    (let [sql (format "DROP SCHEMA IF EXISTS \"%s\" CASCADE;" (unique-session-schema))]
+      (log/info (u/format-color 'blue "[redshift] %s" sql))
+      (.execute stmt sql))))
+
+(defmethod tx/after-run :redshift
+  [driver]
+  (sql-jdbc.execute/do-with-connection-with-options
+   driver
+   (sql-jdbc.conn/connection-details->spec driver @db-connection-details)
+   {:write? true}
+   delete-session-schema!))
+
 (defonce ^:private ^{:arglists '([driver connection metadata _ _])}
   original-filtered-syncable-schemas
   (get-method sql-jdbc.sync/filtered-syncable-schemas :redshift))
diff --git a/test/metabase/query_processor/card_test.clj b/test/metabase/query_processor/card_test.clj
index 5b415a2c3c5255eec0046abec4065f1f49aaaf19..c9e1e828a9da7e04e8f6cf687ccd678c4ece4f38 100644
--- a/test/metabase/query_processor/card_test.clj
+++ b/test/metabase/query_processor/card_test.clj
@@ -115,7 +115,7 @@
       (is (= {"id" :number}
              (#'qp.card/card-template-tag-parameters card-id))))))
 
-(deftest infer-parameter-name-test
+(deftest ^:parallel infer-parameter-name-test
   (is (= "my_param"
          (#'qp.card/infer-parameter-name {:name "my_param", :target [:variable [:template-tag :category]]})))
   (is (= "category"
diff --git a/test/metabase/test/data/interface.clj b/test/metabase/test/data/interface.clj
index 4cdb17abe4d313b3df128e3b7dfbe64d0d86fb3f..1556a52ec9bb3079fd6355eec4dea97fa3b19491 100644
--- a/test/metabase/test/data/interface.clj
+++ b/test/metabase/test/data/interface.clj
@@ -9,6 +9,7 @@
    [clojure.string :as str]
    [clojure.tools.reader.edn :as edn]
    [environ.core :as env]
+   [mb.hawk.hooks]
    [mb.hawk.init]
    [medley.core :as m]
    [metabase.config :as config]
@@ -20,12 +21,14 @@
    [metabase.models.table :refer [Table]]
    [metabase.plugins.classloader :as classloader]
    [metabase.query-processor :as qp]
+   [metabase.test.data.env :as tx.env]
    [metabase.test.initialize :as initialize]
    [metabase.util :as u]
    [metabase.util.date-2 :as u.date]
    [metabase.util.log :as log]
    [metabase.util.malli :as mu]
    [metabase.util.malli.schema :as ms]
+   [methodical.core :as methodical]
    [potemkin.types :as p.types]
    [pretty.core :as pretty]
    [toucan2.core :as t2]))
@@ -122,7 +125,7 @@
 
 (defonce ^:private has-done-before-run (atom #{}))
 
-;; this gets called below by `load-test-extensions-namespace-if-needed`
+;; this gets called below by [[load-test-extensions-namespace-if-needed]]
 (defn- do-before-run-if-needed [driver]
   (when-not (@has-done-before-run driver)
     (locking has-done-before-run
@@ -134,7 +137,6 @@
         ((get-method before-run driver) driver)
         (swap! has-done-before-run conj driver)))))
 
-
 (defn- require-driver-test-extensions-ns [driver & require-options]
   (let [expected-ns (symbol (or (namespace driver)
                                 (str "metabase.test.data." (name driver))))]
@@ -277,6 +279,16 @@
                  :engine (u/qualified-name driver)
                  {:order-by [[:id :asc]]}))
 
+(declare after-run)
+
+(methodical/defmethod mb.hawk.hooks/after-run ::run-drivers-after-run
+  "Run [[metabase.test.data.interface/after-run]] methods for drivers."
+  [_options]
+  (doseq [driver (tx.env/test-drivers)
+          :when  (isa? driver/hierarchy driver ::test-extensions)]
+    (log/infof "Running after-run hooks for %s..." driver)
+    (after-run driver)))
+
 
 ;;; +----------------------------------------------------------------------------------------------------------------+
 ;;; |                                            Interface (Multimethods)                                            |
@@ -295,7 +307,24 @@
   dispatch-on-driver-with-test-extensions
   :hierarchy #'driver/hierarchy)
 
-(defmethod before-run ::test-extensions [_]) ; default-impl is a no-op
+(defmethod before-run ::test-extensions [_]) ; default impl is a no-op
+
+(defmulti after-run
+  "Do cleanup after the test suite finishes running for a driver, when running from the CLI (this is not done when
+  running tests from the REPL). This is a good place to clean up after yourself, e.g. delete any cloud databases you
+  no longer need.
+
+  Will only be called once for a given driver; only called when running tests against that driver. This method does
+  not need to call the implementation for any parent drivers; that is done automatically.
+
+  DO NOT CALL THIS METHOD DIRECTLY; THIS IS CALLED AUTOMATICALLY WHEN APPROPRIATE."
+  {:arglists '([driver]), :added "0.49.0"}
+  dispatch-on-driver-with-test-extensions
+  :hierarchy #'driver/hierarchy)
+
+(defmethod after-run ::test-extensions
+  [driver]
+  (log/infof "%s has no after-run hooks." driver))
 
 (defmulti dbdef->connection-details
   "Return the connection details map that should be used to connect to the Database we will create for
diff --git a/test_config/log4j2-test.xml b/test_config/log4j2-test.xml
index 533b960437d144515fc54a5671427e43d605a2be..aa899da4e81491fb8b9673bce9b1e3a6ea7a84fe 100644
--- a/test_config/log4j2-test.xml
+++ b/test_config/log4j2-test.xml
@@ -9,6 +9,7 @@
     <Logger name="metabase" level="FATAL"/>
     <Logger name="metabase-enterprise" level="FATAL"/>
     <Logger name="liquibase" level="FATAL"/>
+    <Logger name="metabase.test.data.interface" level="INFO"/>
 
     <Root level="FATAL">
       <AppenderRef ref="Console"/>