diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
index adcf7fd10..558b3e9b9 100644
--- a/src/libexpr/flake/config.cc
+++ b/src/libexpr/flake/config.cc
@@ -53,7 +53,7 @@ void ConfigFile::apply()
 
         bool trusted = whitelist.count(baseName);
         if (!trusted) {
-            switch (nix::fetchSettings.acceptFlakeConfig) {
+            switch (nix::fetchSettings.acceptFlakeConfig.get()) {
             case AcceptFlakeConfig::True: {
                 trusted = true;
                 break;
diff --git a/src/libexpr/parser/parser.cc b/src/libexpr/parser/parser.cc
index a00586c36..b7a105fe7 100644
--- a/src/libexpr/parser/parser.cc
+++ b/src/libexpr/parser/parser.cc
@@ -12,7 +12,6 @@
 #include "state.hh"
 
 #include <charconv>
-#include <clocale>
 #include <memory>
 
 // flip this define when doing parser development to enable some g checks.
@@ -254,7 +253,8 @@ struct AttrState : SubexprState {
 
     std::vector<AttrName> attrs;
 
-    void pushAttr(auto && attr, PosIdx) { attrs.emplace_back(std::move(attr)); }
+    template <typename T>
+    void pushAttr(T && attr, PosIdx) { attrs.emplace_back(std::forward<T>(attr)); }
 };
 
 template<> struct BuildAST<grammar::attr::simple> {
@@ -290,7 +290,8 @@ struct InheritState : SubexprState {
     std::unique_ptr<Expr> from;
     PosIdx fromPos;
 
-    void pushAttr(auto && attr, PosIdx pos) { attrs.emplace_back(std::move(attr), pos); }
+    template <typename T>
+    void pushAttr(T && attr, PosIdx pos) { attrs.emplace_back(std::forward<T>(attr), pos); }
 };
 
 template<> struct BuildAST<grammar::inherit::from> {
diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc
index fcb947f96..566dc65d4 100644
--- a/src/libstore/filetransfer.cc
+++ b/src/libstore/filetransfer.cc
@@ -477,8 +477,17 @@ struct curlFileTransfer : public FileTransfer
 
     ~curlFileTransfer()
     {
-        stopWorkerThread();
-
+        try {
+            stopWorkerThread();
+        } catch (nix::Error e) {
+            // This can only fail if a socket to our own process cannot be
+            // written to, so it is always a bug in the program if it fails.
+            //
+            // Joining the thread would probably only cause a deadlock if this
+            // happened, so just die on purpose.
+            printError("failed to join curl file transfer worker thread: %1%", e.what());
+            std::terminate();
+        }
         workerThread.join();
 
         if (curlm) curl_multi_cleanup(curlm);
diff --git a/src/libstore/machines.cc b/src/libstore/machines.cc
index 833482815..d0897b81f 100644
--- a/src/libstore/machines.cc
+++ b/src/libstore/machines.cc
@@ -33,7 +33,7 @@ Machine::Machine(decltype(storeUri) storeUri,
     systemTypes(systemTypes),
     sshKey(sshKey),
     maxJobs(maxJobs),
-    speedFactor(speedFactor == 0.0f ? 1.0f : std::move(speedFactor)),
+    speedFactor(speedFactor == 0.0f ? 1.0f : speedFactor),
     supportedFeatures(supportedFeatures),
     mandatoryFeatures(mandatoryFeatures),
     sshPublicHostKey(sshPublicHostKey)
diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc
index 5fb33ef56..d4da18f14 100644
--- a/src/libutil/archive.cc
+++ b/src/libutil/archive.cc
@@ -192,7 +192,7 @@ static Generator<Entry> parseObject(Source & source, const Path & path)
 #define EXPECT(raw, kind)                              \
     do {                                               \
         const auto s = readString(source);             \
-        if (s != raw) {                                \
+        if (s != (raw)) {                                \
             throw badArchive("expected " kind " tag"); \
         }                                              \
         co_yield MetadataString{s};                    \
diff --git a/src/libutil/file-descriptor.cc b/src/libutil/file-descriptor.cc
index ab69b5754..037cd5297 100644
--- a/src/libutil/file-descriptor.cc
+++ b/src/libutil/file-descriptor.cc
@@ -131,7 +131,7 @@ AutoCloseFD::AutoCloseFD(AutoCloseFD && that) : fd{that.fd}
 }
 
 
-AutoCloseFD & AutoCloseFD::operator =(AutoCloseFD && that)
+AutoCloseFD & AutoCloseFD::operator =(AutoCloseFD && that) noexcept(false)
 {
     close();
     fd = that.fd;
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index a762dc940..0ce82f273 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -229,7 +229,7 @@ Hash::Hash(std::string_view rest, HashType type, bool isSRI)
 
         for (unsigned int n = 0; n < rest.size(); ++n) {
             char c = rest[rest.size() - n - 1];
-            unsigned char digit;
+            size_t digit;
             for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */
                 if (base32Chars[digit] == c) break;
             if (digit >= 32)
diff --git a/src/libutil/shlex.cc b/src/libutil/shlex.cc
index 21fa0502a..b923fef65 100644
--- a/src/libutil/shlex.cc
+++ b/src/libutil/shlex.cc
@@ -62,6 +62,8 @@ std::vector<std::string> shell_split(const std::string & input)
                 begin = ++iterator;
             }
             break;
+            // no other relevant cases; silence exhaustiveness compiler warning
+            default: break;
         }
     }
 
diff --git a/src/libutil/url.cc b/src/libutil/url.cc
index 87146ca56..2de50dd4d 100644
--- a/src/libutil/url.cc
+++ b/src/libutil/url.cc
@@ -63,7 +63,7 @@ std::string percentDecode(std::string_view in)
             if (i + 2 >= in.size())
                 throw BadURL("invalid URI parameter '%s'", in);
             try {
-                decoded += std::stoul(std::string(in, i + 1, 2), 0, 16);
+                decoded += char8_t(std::stoul(std::string(in, i + 1, 2), 0, 16));
                 i += 3;
             } catch (...) {
                 throw BadURL("invalid URI parameter '%s'", in);
diff --git a/tests/unit/libutil-support/tests/cli-literate-parser.cc b/tests/unit/libutil-support/tests/cli-literate-parser.cc
index f74fe85eb..3aa55859c 100644
--- a/tests/unit/libutil-support/tests/cli-literate-parser.cc
+++ b/tests/unit/libutil-support/tests/cli-literate-parser.cc
@@ -1,20 +1,16 @@
 #include "cli-literate-parser.hh"
 #include "escape-string.hh"
-#include "escape-char.hh"
-#include "libexpr/print.hh"
 #include "types.hh"
 #include <ranges>
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/algorithm/string/trim.hpp>
 #include <iostream>
-#include <memory>
 #include <sstream>
 #include <variant>
 
 #include "cli-literate-parser.hh"
 #include "escape-string.hh"
 #include "fmt.hh"
-#include "libexpr/print.hh"
 #include "shlex.hh"
 #include "types.hh"
 #include "strings.hh"
@@ -361,9 +357,8 @@ const char * ParseError::what() const noexcept
         return what_->c_str();
     } else {
         auto escaped = escapeString(rest, {.maxLength = 256, .escapeNonPrinting = true});
-        auto hint =
-            new HintFmt("Parse error: Expected %1%, got:\n%2%", expected, Uncolored(escaped));
-        what_ = hint->str();
+        auto hint = HintFmt("Parse error: Expected %1%, got:\n%2%", expected, Uncolored(escaped));
+        what_ = hint.str();
         return what_->c_str();
     }
 }