diff --git a/Annex/Common.hs b/Annex/Common.hs
--- a/Annex/Common.hs
+++ b/Annex/Common.hs
@@ -12,6 +12,7 @@
 import Messages as X
 import Git.Quote as X
 import Types.RepoSize as X
+import Git.Types as X (RemoteName)
 #ifndef mingw32_HOST_OS
 import System.Posix.IO as X hiding (createPipe, append)
 #endif
diff --git a/Annex/Content/PointerFile.hs b/Annex/Content/PointerFile.hs
--- a/Annex/Content/PointerFile.hs
+++ b/Annex/Content/PointerFile.hs
@@ -1,6 +1,6 @@
 {- git-annex pointer files
  -
- - Copyright 2010-2018 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -33,21 +33,28 @@
 populatePointerFile :: Restage -> Key -> OsPath -> OsPath -> Annex (Maybe InodeCache)
 populatePointerFile restage k obj f = go =<< liftIO (isPointerFile f)
   where
-	go (Just k') | k == k' = do
-		destmode <- liftIO $ catchMaybeIO $
-			fileMode <$> R.getFileStatus (fromOsPath f)
-		liftIO $ removeWhenExistsWith removeFile f
-		(ic, populated) <- replaceWorkTreeFile f $ \tmp -> do
-			ok <- linkOrCopy k obj tmp destmode >>= \case
-				Just _ -> thawContent tmp >> return True
-				Nothing -> liftIO (writePointerFile tmp k destmode) >> return False
-			ic <- withTSDelta (liftIO . genInodeCache tmp)
-			return (ic, ok)
-		maybe noop (restagePointerFile restage f) ic
-		if populated
-			then return ic
-			else return Nothing
+	go (Just k') | k == k' = populatePointerFile' restage k obj f
 	go _ = return Nothing
+
+{- Before calling, must verify that the pointer file is a pointer to the key.
+ -
+ - This returns Nothing when populating the pointer file fails due to eg,
+ - not enough disk space.
+ -}
+populatePointerFile' :: Restage -> Key -> OsPath -> OsPath -> Annex (Maybe InodeCache)
+populatePointerFile' restage k obj f = do
+	destmode <- liftIO $ catchMaybeIO $
+		fileMode <$> R.getFileStatus (fromOsPath f)
+	(ic, populated) <- replaceWorkTreeFile f $ \tmp -> do
+		ok <- linkOrCopy k obj tmp destmode >>= \case
+			Just _ -> thawContent tmp >> return True
+			Nothing -> liftIO (writePointerFile tmp k destmode) >> return False
+		ic <- withTSDelta (liftIO . genInodeCache tmp)
+		return (ic, ok)
+	maybe noop (restagePointerFile restage f) ic
+	if populated
+		then return ic
+		else return Nothing
 
 {- Removes the content from a pointer file, replacing it with a pointer.
  -
diff --git a/Annex/Import.hs b/Annex/Import.hs
--- a/Annex/Import.hs
+++ b/Annex/Import.hs
@@ -798,7 +798,7 @@
 					Right r -> next $ do
 						liftIO $ atomically $
 							putTMVar job r
-						return True
+						return (isJust r)
 			commandAction $ bracket_
 				(waitstart importing cid)
 				(signaldone importing cid)
@@ -982,14 +982,23 @@
 			ImportSubTree subdir _ ->
 				getTopFilePath subdir </> fromImportLocation loc
 
-	getcidkey cidmap db cid = liftIO $
+	getcidkey cidmap db cid = do
 		-- Avoiding querying the database when it's empty speeds up
 		-- the initial import.
-		if CIDDb.databaseIsEmpty db
+		l <- liftIO $ if CIDDb.databaseIsEmpty db
 			then getcidkeymap cidmap cid
 			else CIDDb.getContentIdentifierKeys db rs cid >>= \case
 				[] -> getcidkeymap cidmap cid
 				l -> return l
+		filterM validcidkey l
+	
+	-- Guard against a content identifier containing a git sha that is
+	-- not present in the repository. This can happen when a previous,
+	-- import failed and the tree was not recorded, and this import is
+	-- being run in another clone of the repository.
+	validcidkey k = case keyGitSha k of
+		Just sha -> isJust <$> catObjectMetaData sha
+		Nothing -> return True
 
 	getcidkeymap cidmap cid =
 		atomically $ maybeToList . M.lookup cid <$> readTVar cidmap
diff --git a/Annex/Init.hs b/Annex/Init.hs
--- a/Annex/Init.hs
+++ b/Annex/Init.hs
@@ -1,6 +1,6 @@
 {- git-annex repository initialization
  -
- - Copyright 2011-2025 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -20,6 +20,7 @@
 	probeCrippledFileSystem,
 	probeCrippledFileSystem',
 	isCrippledFileSystem,
+	propigateInitGitConfigs,
 ) where
 
 import Annex.Common
@@ -34,6 +35,8 @@
 import Logs.UUID
 import Logs.Trust.Basic
 import Logs.Config
+import Logs.PreferredContent.Raw
+import Logs.Group
 import Types.TrustLevel
 import Types.RepoVersion
 import Annex.Version
@@ -64,6 +67,7 @@
 #endif
 
 import qualified Data.Map as M
+import qualified Data.Set as S
 import Control.Monad.IO.Class (MonadIO)
 #ifndef mingw32_HOST_OS
 import System.PosixCompat.Files (ownerReadMode, isNamedPipe)
@@ -150,7 +154,8 @@
 		hookWrite preCommitHook
 		hookWrite postReceiveHook
 	setDifferences
-	unlessM (isJust <$> getVersion) $
+	initialversion <- getVersion
+	unless (isJust initialversion) $
 		setVersion (fromMaybe defaultVersion mversion)
 	supportunlocked <- annexSupportUnlocked <$> Annex.getGitConfig
 	if supportunlocked
@@ -171,6 +176,8 @@
 					Direct.switchHEADBack
 				)
 	propigateSecureHashesOnly
+	when (isNothing initialversion) $
+		propigateInitGitConfigs =<< getUUID
 	createInodeSentinalFile False
 	fixupUnusualReposAfterInit
 
@@ -487,7 +494,7 @@
 	trustSet u UnTrusted
 	setConfig (annexConfig "hardlink") (Git.Config.boolConfig True)
 
-{- Propagate annex.securehashesonly from then global config to local
+{- Propigate annex.securehashesonly from the global config to local
  - config. This makes a clone inherit a parent's setting, but once
  - a repository has a local setting, changes to the global config won't
  - affect it. -}
@@ -495,6 +502,19 @@
 propigateSecureHashesOnly =
 	maybe noop (setConfig "annex.securehashesonly" . fromConfigValue)
 		=<< getGlobalConfig "annex.securehashesonly"
+
+{- Propigate git configs that set defaults. -}
+propigateInitGitConfigs :: UUID -> Annex ()
+propigateInitGitConfigs u = do
+	gc <- Annex.getGitConfig
+	set (annexInitWanted gc) preferredContentSet
+	set (annexInitRequired gc) requiredContentSet
+	case annexInitGroups gc of
+		[] -> noop
+		groups -> groupChange u (S.union (S.fromList groups))
+  where
+	set (Just expr) setter = setter u expr
+	set Nothing _ = noop
 
 fixupUnusualReposAfterInit :: Annex ()
 fixupUnusualReposAfterInit = do
diff --git a/Annex/Journal.hs b/Annex/Journal.hs
--- a/Annex/Journal.hs
+++ b/Annex/Journal.hs
@@ -13,7 +13,6 @@
  -}
 
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE CPP #-}
 
 module Annex.Journal where
 
@@ -211,14 +210,9 @@
 discardIncompleteAppend v
 	| L.null v = v
 	| L.last v == nl = v
-	| otherwise = dropwhileend (/= nl) v
+	| otherwise = L.dropWhileEnd (/= nl) v
   where
 	nl = fromIntegral (ord '\n')
-#if MIN_VERSION_bytestring(0,11,2)
-	dropwhileend = L.dropWhileEnd
-#else
-	dropwhileend p = L.reverse . L.dropWhile p . L.reverse
-#endif
 
 {- List of existing journal files in a journal directory, but without locking,
  - may miss new ones just being added, or may have false positives if the
diff --git a/Annex/Link.hs b/Annex/Link.hs
--- a/Annex/Link.hs
+++ b/Annex/Link.hs
@@ -47,12 +47,8 @@
 import qualified Data.ByteString.Lazy as L
 
 #ifndef mingw32_HOST_OS
-#if MIN_VERSION_unix(2,8,0)
 import Utility.OpenFd
-#else
-import System.PosixCompat.Files (isSymbolicLink)
 #endif
-#endif
 
 type LinkTarget = S.ByteString
 
@@ -455,19 +451,12 @@
 #if defined(mingw32_HOST_OS)
 	F.withFile f ReadMode readhandle
 #else
-#if MIN_VERSION_unix(2,8,0)
 	let open = do
 		fd <- openFdWithMode (fromOsPath f) ReadOnly Nothing
 			(defaultFileFlags { nofollow = True })
 			(CloseOnExecFlag True)
 		fdToHandle fd
 	in bracket open hClose readhandle
-#else
-	ifM (isSymbolicLink <$> R.getSymbolicLinkStatus (fromOsPath f))
-		( return Nothing
-		, F.withFile f ReadMode readhandle
-		)
-#endif
 #endif
   where
 	readhandle h = parseLinkTargetOrPointer <$> S.hGet h maxPointerSz
diff --git a/Annex/Locations.hs b/Annex/Locations.hs
--- a/Annex/Locations.hs
+++ b/Annex/Locations.hs
@@ -796,11 +796,7 @@
 
 	needesc = map (fromIntegral . ord) ['&', '%', ':', '/']
 
-#if MIN_VERSION_bytestring(0,11,3)
 	anyneedesc = SB.any (`elem` needesc)
-#else
-	anyneedesc = any (`elem` needesc) . SB.unpack
-#endif
 
 {- Reverses keyFile, converting a filename fragment (ie, the basename of
  - the symlink target) into a key. -}
diff --git a/Annex/Proxy.hs b/Annex/Proxy.hs
--- a/Annex/Proxy.hs
+++ b/Annex/Proxy.hs
@@ -29,7 +29,6 @@
 import Logs.Location
 import Utility.Tmp.Dir
 import Utility.Metered
-import Git.Types
 import qualified Database.Export as Export
 import qualified Utility.FileIO as F
 #ifndef mingw32_HOST_OS
diff --git a/Annex/Sim.hs b/Annex/Sim.hs
--- a/Annex/Sim.hs
+++ b/Annex/Sim.hs
@@ -9,7 +9,7 @@
 
 module Annex.Sim where
 
-import Annex.Common
+import Annex.Common hiding (RemoteName)
 import Utility.DataUnits
 import Types.NumCopies
 import Types.Group
diff --git a/Annex/SpecialRemote.hs b/Annex/SpecialRemote.hs
--- a/Annex/SpecialRemote.hs
+++ b/Annex/SpecialRemote.hs
@@ -22,7 +22,6 @@
 import Logs.Remote
 import Logs.Trust
 import qualified Types.Remote as Remote
-import Git.Types (RemoteName)
 import Utility.SafeOutput
 
 import qualified Data.Map as M
@@ -99,7 +98,7 @@
 			(Just name, Right t) -> checkcanenable u name $ do
 				showSideAction $ UnquotedString $ "Auto enabling special remote " ++ name
 				dummycfg <- liftIO dummyRemoteGitConfig
-				tryNonAsync (setup t (AutoEnable c) (Just u) Nothing c dummycfg) >>= \case
+				tryNonAsync (setup t (AutoEnable c) (Just u) name Nothing c dummycfg) >>= \case
 					Left e -> warning (UnquotedString (show e))
 					Right (_c, _u) ->
 						when (cu /= u) $
diff --git a/Annex/Url.hs b/Annex/Url.hs
--- a/Annex/Url.hs
+++ b/Annex/Url.hs
@@ -157,7 +157,7 @@
 withUrlOptions mgc a = a =<< getUrlOptions mgc
 
 -- When downloading an url, if authentication is needed, uses
--- git-credential to prompt for username and password.
+-- git-credential for the prompting.
 --
 -- Note that, when the downloader is curl, it will not use git-credential.
 -- If the user wants to, they can configure curl to use a netrc file that
@@ -169,8 +169,8 @@
 	prompter <- mkPrompter
 	cc <- Annex.getRead Annex.gitcredentialcache
 	a $ uo
-		{ U.getBasicAuth = \u -> prompter $
-			getBasicAuthFromCredential g cc u
+		{ U.getBasicAuth = \u respheaders -> prompter $
+			getBasicAuthFromCredential g cc u respheaders
 		}
 
 checkBoth :: U.URLString -> Maybe Integer -> U.UrlOptions -> Annex Bool
diff --git a/Assistant.hs b/Assistant.hs
--- a/Assistant.hs
+++ b/Assistant.hs
@@ -40,9 +40,6 @@
 #ifdef WITH_WEBAPP
 import Assistant.WebApp
 import Assistant.Threads.WebApp
-#ifdef WITH_PAIRING
-import Assistant.Threads.PairListener
-#endif
 #else
 import Assistant.Types.UrlRenderer
 #endif
@@ -155,11 +152,6 @@
 			then webappthread
 			else webappthread ++
 				[ watch commitThread
-#ifdef WITH_WEBAPP
-#ifdef WITH_PAIRING
-				, assist $ pairListenerThread urlrenderer
-#endif
-#endif
 				, assist pushThread
 				, assist pushRetryThread
 				, assist exportThread
diff --git a/Assistant/Alert.hs b/Assistant/Alert.hs
--- a/Assistant/Alert.hs
+++ b/Assistant/Alert.hs
@@ -16,7 +16,6 @@
 import Utility.Tense
 import Types.Transfer
 import Types.Distribution
-import Git.Types (RemoteName)
 
 import Data.String
 import qualified Data.Text as T
diff --git a/Assistant/MakeRemote.hs b/Assistant/MakeRemote.hs
--- a/Assistant/MakeRemote.hs
+++ b/Assistant/MakeRemote.hs
@@ -24,7 +24,6 @@
 import Logs.UUID
 import Logs.Remote
 import Git.Remote
-import Git.Types (RemoteName)
 import Creds
 import Assistant.Gpg
 import Utility.Gpg (KeyId)
@@ -108,7 +107,7 @@
 	 - to perform IO actions to refill the pool. -}
 	let weakc = M.insert (Proposed "highRandomQuality") (Proposed "false") (M.union config c)
 	dummycfg <- liftIO dummyRemoteGitConfig
-	(c', u) <- R.setup remotetype ss mu mcreds weakc dummycfg
+	(c', u) <- R.setup remotetype ss mu name mcreds weakc dummycfg
 	case mcu of
 		Nothing ->
 			configSet u c'
diff --git a/Assistant/Pairing/MakeRemote.hs b/Assistant/Pairing/MakeRemote.hs
deleted file mode 100644
--- a/Assistant/Pairing/MakeRemote.hs
+++ /dev/null
@@ -1,98 +0,0 @@
-{- git-annex assistant pairing remote creation
- -
- - Copyright 2012 Joey Hess <id@joeyh.name>
- -
- - Licensed under the GNU AGPL version 3 or higher.
- -}
-
-module Assistant.Pairing.MakeRemote where
-
-import Assistant.Common
-import Assistant.Ssh
-import Assistant.Pairing
-import Assistant.Pairing.Network
-import Assistant.MakeRemote
-import Assistant.Sync
-import Config.Cost
-import Config
-import qualified Types.Remote as Remote
-
-import Network.Socket
-import qualified Data.Text as T
-
-{- Authorized keys are set up before pairing is complete, so that the other
- - side can immediately begin syncing. -}
-setupAuthorizedKeys :: PairMsg -> OsPath -> IO ()
-setupAuthorizedKeys msg repodir = case validateSshPubKey $ remoteSshPubKey $ pairMsgData msg of
-	Left err -> giveup err
-	Right pubkey -> do
-		absdir <- absPath repodir
-		unlessM (liftIO $ addAuthorizedKeys True absdir pubkey) $
-			giveup "failed setting up ssh authorized keys"
-
-{- When local pairing is complete, this is used to set up the remote for
- - the host we paired with. -}
-finishedLocalPairing :: PairMsg -> SshKeyPair -> Assistant ()
-finishedLocalPairing msg keypair = do
-	sshdata <- liftIO $ installSshKeyPair keypair =<< pairMsgToSshData msg
-	{- Ensure that we know the ssh host key for the host we paired with.
-	 - If we don't, ssh over to get it. -}
-	liftIO $ unlessM (knownHost $ sshHostName sshdata) $
-		void $ sshTranscript
-			[ sshOpt "StrictHostKeyChecking" "no"
-			, sshOpt "NumberOfPasswordPrompts" "0"
-			, "-n"
-			]
-			(genSshHost (sshHostName sshdata) (sshUserName sshdata))
-			("git-annex-shell -c configlist " ++ T.unpack (sshDirectory sshdata))
-			Nothing
-	r <- liftAnnex $ addRemote $ makeSshRemote sshdata
-	repo <- liftAnnex $ Remote.getRepo r
-	liftAnnex $ setRemoteCost repo semiExpensiveRemoteCost
-	syncRemote r
-
-{- Mostly a straightforward conversion.  Except:
- -  * Determine the best hostname to use to contact the host.
- -  * Strip leading ~/ from the directory name.
- -}
-pairMsgToSshData :: PairMsg -> IO SshData
-pairMsgToSshData msg = do
-	let d = pairMsgData msg
-	hostname <- liftIO $ bestHostName msg
-	let dir = case remoteDirectory d of
-		('~':'/':v) -> v
-		v -> v
-	return SshData
-		{ sshHostName = T.pack hostname
-		, sshUserName = Just (T.pack $ remoteUserName d)
-		, sshDirectory = T.pack dir
-		, sshRepoName = genSshRepoName hostname (toOsPath dir)
-		, sshPort = 22
-		, needsPubKey = True
-		, sshCapabilities = [GitAnnexShellCapable, GitCapable, RsyncCapable]
-		, sshRepoUrl = Nothing
-		}
-
-{- Finds the best hostname to use for the host that sent the PairMsg.
- -
- - If remoteHostName is set, tries to use a .local address based on it.
- - That's the most robust, if this system supports .local.
- - Otherwise, looks up the hostname in the DNS for the remoteAddress,
- - if any. May fall back to remoteAddress if there's no DNS. Ugh. -}
-bestHostName :: PairMsg -> IO HostName
-bestHostName msg = case remoteHostName $ pairMsgData msg of
-	Just h -> do
-		let localname = h ++ ".local"
-		addrs <- catchDefaultIO [] $
-			getAddrInfo Nothing (Just localname) Nothing
-		maybe fallback (const $ return localname) (headMaybe addrs)
-	Nothing -> fallback
-  where
-	fallback = do
-		let a = pairMsgAddr msg
-		let sockaddr = case a of
-			IPv4Addr addr -> SockAddrInet (fromInteger 0) addr
-			IPv6Addr addr -> SockAddrInet6 (fromInteger 0) 0 addr 0
-		fromMaybe (showAddr a)
-			<$> catchDefaultIO Nothing
-				(fst <$> getNameInfo [] True False sockaddr)
diff --git a/Assistant/Pairing/Network.hs b/Assistant/Pairing/Network.hs
deleted file mode 100644
--- a/Assistant/Pairing/Network.hs
+++ /dev/null
@@ -1,132 +0,0 @@
-{- git-annex assistant pairing network code
- -
- - All network traffic is sent over multicast UDP. For reliability,
- - each message is repeated until acknowledged. This is done using a
- - thread, that gets stopped before the next message is sent.
- -
- - Copyright 2012 Joey Hess <id@joeyh.name>
- -
- - Licensed under the GNU AGPL version 3 or higher.
- -}
-
-module Assistant.Pairing.Network where
-
-import Assistant.Common
-import Assistant.Pairing
-import Assistant.DaemonStatus
-import Utility.ThreadScheduler
-import Utility.Verifiable
-
-import Network.Multicast
-import Network.Info
-import Network.Socket
-import qualified Network.Socket.ByteString as B
-import qualified Data.ByteString.UTF8 as BU8
-import qualified Data.Map as M
-import Control.Concurrent
-
-{- This is an arbitrary port in the dynamic port range, that could
- - conceivably be used for some other broadcast messages.
- - If so, hope they ignore the garbage from us; we'll certainly
- - ignore garbage from them. Wild wild west. -}
-pairingPort :: PortNumber
-pairingPort = 55556
-
-{- Goal: Reach all hosts on the same network segment.
- - Method: Use same address that avahi uses. Other broadcast addresses seem
- - to not be let through some routers. -}
-multicastAddress :: AddrClass -> HostName
-multicastAddress IPv4AddrClass = "224.0.0.251"
-multicastAddress IPv6AddrClass = "ff02::fb"
-
-{- Multicasts a message repeatedly on all interfaces, with a 2 second
- - delay between each transmission. The message is repeated forever
- - unless a number of repeats is specified.
- -
- - The remoteHostAddress is set to the interface's IP address.
- -
- - Note that new sockets are opened each time. This is hardly efficient,
- - but it allows new network interfaces to be used as they come up.
- - On the other hand, the expensive DNS lookups are cached.
- -}
-multicastPairMsg :: Maybe Int -> Secret -> PairData -> PairStage -> IO ()
-multicastPairMsg repeats secret pairdata stage = go M.empty repeats
-  where
-	go _ (Just 0) = noop
-	go cache n = do
-		addrs <- activeNetworkAddresses
-		let cache' = updatecache cache addrs
-		mapM_ (sendinterface cache') addrs
-		threadDelaySeconds (Seconds 2)
-		go cache' $ pred <$> n
-	{- The multicast library currently chokes on ipv6 addresses. -}
-	sendinterface _ (IPv6Addr _) = noop
-	sendinterface cache i = void $ tryIO $
-		withSocketsDo $ bracket setup cleanup use
-	  where
-		setup = multicastSender (multicastAddress IPv4AddrClass) pairingPort
-		cleanup (sock, _) = close sock -- FIXME does not work
-		use (sock, addr) = do
-			setInterface sock (showAddr i)
-			maybe noop
-				(\s -> void $ B.sendTo sock (BU8.fromString s) addr)
-				(M.lookup i cache)
-	updatecache cache [] = cache
-	updatecache cache (i:is)
-		| M.member i cache = updatecache cache is
-		| otherwise = updatecache (M.insert i (show $ mkmsg i) cache) is
-	mkmsg addr = PairMsg $
-		mkVerifiable (stage, pairdata, addr) secret
-
-startSending :: PairingInProgress -> PairStage -> (PairStage -> IO ()) -> Assistant ()
-startSending pip stage sender = do
-	a <- asIO start
-	void $ liftIO $ forkIO a
-  where
-	start = do
-		tid <- liftIO myThreadId
-		let pip' = pip { inProgressPairStage = stage, inProgressThreadId = Just tid }
-		oldpip <- modifyDaemonStatus $
-			\s -> (s { pairingInProgress = Just pip' }, pairingInProgress s)
-		maybe noop stopold oldpip
-		liftIO $ sender stage
-	stopold = maybe noop (liftIO . killThread) . inProgressThreadId
-
-stopSending :: PairingInProgress -> Assistant ()
-stopSending pip = do
-	maybe noop (liftIO . killThread) $ inProgressThreadId pip
-	modifyDaemonStatus_ $ \s -> s { pairingInProgress = Nothing }
-
-class ToSomeAddr a where
-	toSomeAddr :: a -> SomeAddr
-
-instance ToSomeAddr IPv4 where
-	toSomeAddr (IPv4 a) = IPv4Addr a
-
-instance ToSomeAddr IPv6 where
-	toSomeAddr (IPv6 o1 o2 o3 o4) = IPv6Addr (o1, o2, o3, o4)
-
-showAddr :: SomeAddr -> HostName
-showAddr (IPv4Addr a) = show $ IPv4 a
-showAddr (IPv6Addr (o1, o2, o3, o4)) = show $ IPv6 o1 o2 o3 o4
-
-activeNetworkAddresses :: IO [SomeAddr]
-activeNetworkAddresses = filter (not . all (`elem` "0.:") . showAddr)
-	. concatMap (\ni -> [toSomeAddr $ ipv4 ni, toSomeAddr $ ipv6 ni])
-	<$> getNetworkInterfaces
-
-{- A human-visible description of the repository being paired with.
- - Note that the repository's description is not shown to the user, because
- - it could be something like "my repo", which is confusing when pairing
- - with someone else's repo. However, this has the same format as the
- - default description of a repo. -}
-pairRepo :: PairMsg -> String
-pairRepo msg = concat
-	[ remoteUserName d
-	, "@"
-	, fromMaybe (showAddr $ pairMsgAddr msg) (remoteHostName d)
-	, ":"
-	, remoteDirectory d
-	]
-  where
-	d = pairMsgData msg
diff --git a/Assistant/Threads/PairListener.hs b/Assistant/Threads/PairListener.hs
deleted file mode 100644
--- a/Assistant/Threads/PairListener.hs
+++ /dev/null
@@ -1,156 +0,0 @@
-{- git-annex assistant thread to listen for incoming pairing traffic
- -
- - Copyright 2012 Joey Hess <id@joeyh.name>
- -
- - Licensed under the GNU AGPL version 3 or higher.
- -}
-
-{-# LANGUAGE OverloadedStrings #-}
-
-module Assistant.Threads.PairListener where
-
-import Assistant.Common
-import Assistant.Pairing
-import Assistant.Pairing.Network
-import Assistant.Pairing.MakeRemote
-import Assistant.WebApp (UrlRenderer)
-import Assistant.WebApp.Types
-import Assistant.Alert
-import Assistant.DaemonStatus
-import Utility.ThreadScheduler
-import Git
-
-import Network.Multicast
-import Network.Socket
-import qualified Data.ByteString as B
-import qualified Data.ByteString.UTF8 as BU8
-import qualified Network.Socket.ByteString as B
-import qualified Data.Text as T
-
-pairListenerThread :: UrlRenderer -> NamedThread
-pairListenerThread urlrenderer = namedThread "PairListener" $ do
-	listener <- asIO1 $ go [] []
-	liftIO $ withSocketsDo $
-		runEvery (Seconds 60) $ void $ tryIO $ 
-			listener =<< getsock
-  where
-	{- Note this can crash if there's no network interface,
-	 - or only one like lo that doesn't support multicast. -}
-	getsock = multicastReceiver (multicastAddress IPv4AddrClass) pairingPort
-		
-	go reqs cache sock = liftIO (getmsg sock B.empty) >>= \msg -> case readish (BU8.toString msg) of
-		Nothing -> go reqs cache sock
-		Just m -> do
-			debug ["received", show msg]
-			(pip, verified) <- verificationCheck m
-				=<< (pairingInProgress <$> getDaemonStatus)
-			let wrongstage = maybe False (\p -> pairMsgStage m <= inProgressPairStage p) pip
-			let fromus = maybe False (\p -> remoteSshPubKey (pairMsgData m) == remoteSshPubKey (inProgressPairData p)) pip
-			case (wrongstage, fromus, checkSane (pairMsgData m), pairMsgStage m) of
-				(_, True, _, _) -> do
-					debug ["ignoring message that looped back"]
-					go reqs cache sock
-				(_, _, False, _) -> do
-					liftAnnex $ warning $ UnquotedString $
-						"illegal control characters in pairing message; ignoring (" ++ show (pairMsgData m) ++ ")"
-					go reqs cache sock
-				-- PairReq starts a pairing process, so a
-				-- new one is always heeded, even if
-				-- some other pairing is in process.
-				(_, _, _, PairReq) -> if m `elem` reqs
-					then go reqs (invalidateCache m cache) sock
-					else do
-						pairReqReceived verified urlrenderer m
-						go (m:take 10 reqs) (invalidateCache m cache) sock
-				(True, _, _, _) -> do
-					debug
-						["ignoring out of order message"
-						, show (pairMsgStage m)
-						, "expected"
-						, show (succ . inProgressPairStage <$> pip)
-						]
-					go reqs cache sock
-				(_, _, _, PairAck) -> do
-					cache' <- pairAckReceived verified pip m cache
-					go reqs cache' sock
-				(_,_ , _, PairDone) -> do
-					pairDoneReceived verified pip m
-					go reqs cache sock
-
-	{- As well as verifying the message using the shared secret,
-	 - check its UUID against the UUID we have stored. If
-	 - they're the same, someone is sending bogus messages,
-	 - which could be an attempt to brute force the shared secret. -}
-	verificationCheck _ Nothing = return (Nothing, False)
-	verificationCheck m (Just pip)
-		| not verified && sameuuid = do
-			liftAnnex $ warning
-				"detected possible pairing brute force attempt; disabled pairing"
-			stopSending pip
-			return (Nothing, False)
-		| otherwise = return (Just pip, verified && sameuuid)
-	  where
-		verified = verifiedPairMsg m pip
-		sameuuid = pairUUID (inProgressPairData pip) == pairUUID (pairMsgData m)
-
-	{- PairReqs invalidate the cache of recently finished pairings.
-	 - This is so that, if a new pairing is started with the
-	 - same secret used before, a bogus PairDone is not sent. -}
-	invalidateCache msg = filter (not . verifiedPairMsg msg)
-
-	getmsg sock c = do
-		(msg, _) <- B.recvFrom sock chunksz
-		if B.length msg < chunksz
-			then return $ c <> msg
-			else getmsg sock $ c <> msg
-	  where
-		chunksz = 1024
-
-{- Show an alert when a PairReq is seen. -}
-pairReqReceived :: Bool -> UrlRenderer -> PairMsg -> Assistant ()
-pairReqReceived True _ _ = noop -- ignore our own PairReq
-pairReqReceived False urlrenderer msg = do
-	button <- mkAlertButton True (T.pack "Respond") urlrenderer (FinishLocalPairR msg)
-	void $ addAlert $ pairRequestReceivedAlert repo button
-  where
-	repo = pairRepo msg
-
-{- When a verified PairAck is seen, a host is ready to pair with us, and has
- - already configured our ssh key. Stop sending PairReqs, finish the pairing,
- - and send a single PairDone. -}
-pairAckReceived :: Bool -> Maybe PairingInProgress -> PairMsg -> [PairingInProgress] -> Assistant [PairingInProgress]
-pairAckReceived True (Just pip) msg cache = do
-	stopSending pip
-	repodir <- repoPath <$> liftAnnex gitRepo
-	liftIO $ setupAuthorizedKeys msg repodir
-	finishedLocalPairing msg (inProgressSshKeyPair pip)
-	startSending pip PairDone $ multicastPairMsg
-		(Just 1) (inProgressSecret pip) (inProgressPairData pip)
-	return $ pip : take 10 cache
-{- A stale PairAck might also be seen, after we've finished pairing.
- - Perhaps our PairDone was not received. To handle this, we keep
- - a cache of recently finished pairings, and re-send PairDone in
- - response to stale PairAcks for them. -}
-pairAckReceived _ _ msg cache = do
-	let pips = filter (verifiedPairMsg msg) cache
-	unless (null pips) $
-		forM_ pips $ \pip ->
-			startSending pip PairDone $ multicastPairMsg
-				(Just 1) (inProgressSecret pip) (inProgressPairData pip)
-	return cache
-
-{- If we get a verified PairDone, the host has accepted our PairAck, and
- - has paired with us. Stop sending PairAcks, and finish pairing with them.
- -
- - TODO: Should third-party hosts remove their pair request alert when they
- - see a PairDone?
- - Complication: The user could have already clicked on the alert and be
- - entering the secret. Would be better to start a fresh pair request in this
- - situation.
- -}
-pairDoneReceived :: Bool -> Maybe PairingInProgress -> PairMsg -> Assistant ()
-pairDoneReceived False _ _ = noop -- not verified
-pairDoneReceived True Nothing _ = noop -- not in progress
-pairDoneReceived True (Just pip) msg = do
-	stopSending pip
-	finishedLocalPairing msg (inProgressSshKeyPair pip)
diff --git a/Assistant/WebApp/Configurators/AWS.hs b/Assistant/WebApp/Configurators/AWS.hs
--- a/Assistant/WebApp/Configurators/AWS.hs
+++ b/Assistant/WebApp/Configurators/AWS.hs
@@ -21,7 +21,6 @@
 import Types.StandardGroups
 import Creds
 import Assistant.Gpg
-import Git.Types (RemoteName)
 import Annex.SpecialRemote.Config
 import Types.ProposedAccepted
 
diff --git a/Assistant/WebApp/Configurators/Pairing.hs b/Assistant/WebApp/Configurators/Pairing.hs
--- a/Assistant/WebApp/Configurators/Pairing.hs
+++ b/Assistant/WebApp/Configurators/Pairing.hs
@@ -6,25 +6,13 @@
  -}
 
 {-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, OverloadedStrings, FlexibleContexts #-}
-{-# LANGUAGE CPP #-}
 
 module Assistant.WebApp.Configurators.Pairing where
 
-import Assistant.Pairing
 import Assistant.WebApp.Common
-import Annex.UUID
-#ifdef WITH_PAIRING
-import Assistant.DaemonStatus
-import Assistant.Pairing.MakeRemote
-import Assistant.Pairing.Network
-import Assistant.Ssh
-import Utility.Verifiable
-#endif
-import Utility.UserInfo
 import Utility.Tor
 import Utility.Su
 import Assistant.WebApp.Pairing
-import Assistant.Alert
 import qualified Utility.MagicWormhole as Wormhole
 import Assistant.MakeRemote
 import Assistant.RemoteControl
@@ -32,19 +20,11 @@
 import Assistant.WebApp.SideBar
 import Command.P2P (unusedPeerRemoteName, PairingResult(..))
 import P2P.Address
-import Git
 import Annex.Path
 import Utility.Process.Transcript
 
 import qualified Data.Map as M
 import qualified Data.Text as T
-#ifdef WITH_PAIRING
-import qualified Data.Text.Encoding as T
-import qualified Data.ByteString as B
-import Data.Char
-import qualified Control.Exception as E
-import Control.Concurrent
-#endif
 import Control.Concurrent.STM hiding (check)
 
 getStartWormholePairFriendR :: Handler Html
@@ -151,171 +131,5 @@
 		$(widgetFile "configurators/needmagicwormhole")
 	)
 
-{- Starts local pairing. -}
-getStartLocalPairR :: Handler Html
-getStartLocalPairR = postStartLocalPairR
-postStartLocalPairR :: Handler Html
-#ifdef WITH_PAIRING
-postStartLocalPairR = promptSecret Nothing $
-	startLocalPairing PairReq noop pairingAlert Nothing
-#else
-postStartLocalPairR = noLocalPairing
-
-noLocalPairing :: Handler Html
-noLocalPairing = noPairing "local"
-#endif
-
-{- Runs on the system that responds to a local pair request; sets up the ssh
- - authorized key first so that the originating host can immediately sync
- - with us. -}
-getFinishLocalPairR :: PairMsg -> Handler Html
-getFinishLocalPairR = postFinishLocalPairR
-postFinishLocalPairR :: PairMsg -> Handler Html
-#ifdef WITH_PAIRING
-postFinishLocalPairR msg = promptSecret (Just msg) $ \_ secret -> do
-	repodir <- liftH $ repoPath <$> liftAnnex gitRepo
-	liftIO $ setup repodir
-	startLocalPairing PairAck (cleanup repodir) alert uuid "" secret
-  where
-	alert = pairRequestAcknowledgedAlert (pairRepo msg) . Just
-	setup repodir = setupAuthorizedKeys msg repodir
-	cleanup repodir = removeAuthorizedKeys True repodir $
-		remoteSshPubKey $ pairMsgData msg
-	uuid = Just $ pairUUID $ pairMsgData msg
-#else
-postFinishLocalPairR _ = noLocalPairing
-#endif
-
-getRunningLocalPairR :: SecretReminder -> Handler Html
-#ifdef WITH_PAIRING
-getRunningLocalPairR s = pairPage $ do
-	let secret = fromSecretReminder s
-	$(widgetFile "configurators/pairing/local/inprogress")
-#else
-getRunningLocalPairR _ = noLocalPairing
-#endif
-
-#ifdef WITH_PAIRING
-
-{- Starts local pairing, at either the PairReq (initiating host) or 
- - PairAck (responding host) stage.
- -
- - Displays an alert, and starts a thread sending the pairing message,
- - which will continue running until the other host responds, or until
- - canceled by the user. If canceled by the user, runs the oncancel action.
- -
- - Redirects to the pairing in progress page.
- -}
-startLocalPairing :: PairStage -> IO () -> (AlertButton -> Alert) -> Maybe UUID -> Text -> Secret -> Widget
-startLocalPairing stage oncancel alert muuid displaysecret secret = do
-	urlrender <- liftH getUrlRender
-	reldir <- fromJust . relDir <$> liftH getYesod
-
-	sendrequests <- liftAssistant $ asIO2 $ mksendrequests urlrender
-	{- Generating a ssh key pair can take a while, so do it in the
-	 - background. -}
-	thread <- liftAssistant $ asIO $ do
-		keypair <- liftIO $ genSshKeyPair
-		let pubkey = either giveup id $ validateSshPubKey $ sshPubKey keypair
-		pairdata <- liftIO $ PairData
-			<$> getHostname
-			<*> (either giveup id <$> myUserName)
-			<*> pure reldir
-			<*> pure pubkey
-			<*> (maybe genUUID return muuid)
-		let sender = multicastPairMsg Nothing secret pairdata
-		let pip = PairingInProgress secret Nothing keypair pairdata stage
-		startSending pip stage $ sendrequests sender
-	void $ liftIO $ forkIO thread
-
-	liftH $ redirect $ RunningLocalPairR $ toSecretReminder displaysecret
-  where
-	{- Sends pairing messages until the thread is killed,
-	 - and shows an activity alert while doing it.
-	 -
-	 - The cancel button returns the user to the DashboardR. This is
-	 - not ideal, but they have to be sent somewhere, and could
-	 - have been on a page specific to the in-process pairing
-	 - that just stopped, so can't go back there.
-	 -}
-	mksendrequests urlrender sender _stage = do
-		tid <- liftIO myThreadId
-		let selfdestruct = AlertButton
-			{ buttonLabel = "Cancel"
-			, buttonPrimary = True
-			, buttonUrl = urlrender DashboardR
-			, buttonAction = Just $ const $ do
-				oncancel
-				killThread tid
-			}
-		alertDuring (alert selfdestruct) $ liftIO $ do
-			_ <- E.try (sender stage) :: IO (Either E.SomeException ())
-			return ()
-
-data InputSecret = InputSecret { secretText :: Maybe Text }
-
-{- If a PairMsg is passed in, ensures that the user enters a secret
- - that can validate it. -}
-promptSecret :: Maybe PairMsg -> (Text -> Secret -> Widget) -> Handler Html
-promptSecret msg cont = pairPage $ do
-	((result, form), enctype) <- liftH $
-		runFormPostNoToken $ renderBootstrap3 bootstrapFormLayout $
-			InputSecret <$> aopt textField (bfs "Secret phrase") Nothing
-	case result of
-		FormSuccess v -> do
-			let rawsecret = fromMaybe "" $ secretText v
-			let secret = toSecret rawsecret
-			case msg of
-				Nothing -> case secretProblem secret of
-					Nothing -> cont rawsecret secret
-					Just problem ->
-						showform form enctype $ Just problem
-				Just m ->
-					if verify (fromPairMsg m) secret
-						then cont rawsecret secret
-						else showform form enctype $ Just
-							"That's not the right secret phrase."
-		_ -> showform form enctype Nothing
-  where
-	showform form enctype mproblem = do
-		let start = isNothing msg
-		let badphrase = isJust mproblem
-		let problem = fromMaybe "" mproblem
-		let (username, hostname) = maybe ("", "")
-			(\(_, v, a) -> (T.pack $ remoteUserName v, T.pack $ fromMaybe (showAddr a) (remoteHostName v)))
-			(verifiableVal . fromPairMsg <$> msg)
-		u <- liftIO myUserName
-		let sameusername = Right username == (T.pack <$> u)
-		$(widgetFile "configurators/pairing/local/prompt")
-
-{- This counts unicode characters as more than one character,
- - but that's ok; they *do* provide additional entropy. -}
-secretProblem :: Secret -> Maybe Text
-secretProblem s
-	| B.null s = Just "The secret phrase cannot be left empty. (Remember that punctuation and white space is ignored.)"
-	| B.length s < 6 = Just "Enter a longer secret phrase, at least 6 characters, but really, a phrase is best! This is not a password you'll need to enter every day."
-	| s == toSecret sampleQuote = Just "Speaking of foolishness, don't paste in the example I gave. Enter a different phrase, please!"
-	| otherwise = Nothing
-
-toSecret :: Text -> Secret
-toSecret s = T.encodeUtf8 $ T.toLower $ T.filter isAlphaNum s
-
-{- From Dickens -}
-sampleQuote :: Text
-sampleQuote = T.unwords
-	[ "It was the best of times,"
-	, "it was the worst of times,"
-	, "it was the age of wisdom,"
-	, "it was the age of foolishness."
-	]
-
-#else
-
-#endif
-
 pairPage :: Widget -> Handler Html
 pairPage = page "Pairing" (Just Configuration)
-
-noPairing :: Text -> Handler Html
-noPairing pairingtype = pairPage $
-	$(widgetFile "configurators/pairing/disabled")
diff --git a/Assistant/WebApp/Configurators/Ssh.hs b/Assistant/WebApp/Configurators/Ssh.hs
--- a/Assistant/WebApp/Configurators/Ssh.hs
+++ b/Assistant/WebApp/Configurators/Ssh.hs
@@ -21,7 +21,7 @@
 import Utility.Gpg
 import Types.Remote (RemoteConfig)
 import Types.ProposedAccepted
-import Git.Types (RemoteName, fromRef, fromConfigKey)
+import Git.Types (fromRef, fromConfigKey)
 import qualified Remote.GCrypt as GCrypt
 import qualified Annex
 import qualified Git.Command
diff --git a/Assistant/WebApp/Configurators/WebDAV.hs b/Assistant/WebApp/Configurators/WebDAV.hs
--- a/Assistant/WebApp/Configurators/WebDAV.hs
+++ b/Assistant/WebApp/Configurators/WebDAV.hs
@@ -17,7 +17,6 @@
 import Types.Remote (RemoteConfig, config)
 import Types.StandardGroups
 import Logs.Remote
-import Git.Types (RemoteName)
 import Assistant.Gpg
 import Types.GitConfig
 import Annex.SpecialRemote.Config
diff --git a/Assistant/WebApp/Gpg.hs b/Assistant/WebApp/Gpg.hs
--- a/Assistant/WebApp/Gpg.hs
+++ b/Assistant/WebApp/Gpg.hs
@@ -19,7 +19,6 @@
 import qualified Annex.Branch
 import qualified Git.GCrypt
 import qualified Remote.GCrypt as GCrypt
-import Git.Types (RemoteName)
 import Assistant.WebApp.MakeRemote
 import Annex.SpecialRemote.Config
 import Logs.Remote
diff --git a/Assistant/WebApp/MakeRemote.hs b/Assistant/WebApp/MakeRemote.hs
--- a/Assistant/WebApp/MakeRemote.hs
+++ b/Assistant/WebApp/MakeRemote.hs
@@ -18,7 +18,6 @@
 import qualified Config
 import Config.Cost
 import Types.StandardGroups
-import Git.Types (RemoteName)
 import Logs.PreferredContent
 import Assistant.MakeRemote
 
diff --git a/Assistant/WebApp/Pairing.hs b/Assistant/WebApp/Pairing.hs
--- a/Assistant/WebApp/Pairing.hs
+++ b/Assistant/WebApp/Pairing.hs
@@ -12,7 +12,6 @@
 import Command.P2P (wormholePairing, PairingResult(..))
 import P2P.Address
 import Annex.Concurrent
-import Git.Types
 
 import Control.Concurrent
 import Control.Concurrent.Async
diff --git a/Assistant/WebApp/RepoId.hs b/Assistant/WebApp/RepoId.hs
--- a/Assistant/WebApp/RepoId.hs
+++ b/Assistant/WebApp/RepoId.hs
@@ -8,7 +8,6 @@
 module Assistant.WebApp.RepoId where
 
 import Annex.Common
-import Git.Types (RemoteName)
 import qualified Remote
 
 {- Parts of the webapp need to be able to act on repositories that may or
diff --git a/Assistant/WebApp/routes b/Assistant/WebApp/routes
--- a/Assistant/WebApp/routes
+++ b/Assistant/WebApp/routes
@@ -56,10 +56,6 @@
 /config/repository/add/cloud/IA AddIAR GET POST
 /config/repository/add/cloud/glacier AddGlacierR GET POST
 
-/config/repository/pair/local/start StartLocalPairR GET POST
-/config/repository/pair/local/running/#SecretReminder RunningLocalPairR GET
-/config/repository/pair/local/finish/#PairMsg FinishLocalPairR GET POST
-
 /config/repository/pair/wormhole/start/self StartWormholePairSelfR GET
 /config/repository/pair/wormhole/start/friend StartWormholePairFriendR GET
 /config/repository/pair/wormhole/prepare/#PairingWith PrepareWormholePairR GET
diff --git a/BuildFlags.hs b/BuildFlags.hs
--- a/BuildFlags.hs
+++ b/BuildFlags.hs
@@ -26,11 +26,6 @@
 #else
 #warning Building without the webapp.
 #endif
-#ifdef WITH_PAIRING
-	, "Pairing"
-#else
-#warning Building without local pairing.
-#endif
 #ifdef WITH_INOTIFY
 	, "Inotify"
 #endif
@@ -80,7 +75,6 @@
 	, ("uuid", VERSION_uuid)
 	, ("bloomfilter", VERSION_bloomfilter)
 	, ("http-client", VERSION_http_client)
-	, ("persistent-sqlite", VERSION_persistent_sqlite)
 	, ("crypton", VERSION_crypton)
 	, ("aws", VERSION_aws)
 	, ("DAV", VERSION_DAV)
diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,27 @@
+git-annex (10.20260115) upstream; urgency=medium
+
+  * New git configs annex.initwanted, annex.initrequired, and
+    annex.initgroups.
+  * Fix bug that could result in a tree imported from a remote containing
+    missing git blobs.
+  * fix: Populate unlocked pointer files in situations where a git command,
+    like git reset or git stash, leaves them unpopulated.
+  * Pass www-authenticate headers in to to git credential, to support
+    eg, git-credential-oauth.
+  * import: Fix display of some import errors.
+  * external: Respond to GETGITREMOTENAME during INITREMOTE with the remote
+    name.
+  * When displaying sqlite error messages, include the path to the database.
+  * webapp: Remove support for local pairing; use wormhole pairing instead.
+  * git-annex.cabal: Removed pairing build flag, and no longer depends
+    on network-multicast or network-info.
+  * Remove support for building with old versions of persistent and 
+    persistent-sqlite.
+  * Removed support for building with ghc older than 9.6.6.
+  * stack.yaml: Update to lts-24.26.
+
+ -- Joey Hess <id@joeyh.name>  Fri, 16 Jan 2026 11:09:14 -0400
+
 git-annex (10.20251215) upstream; urgency=medium
 
   * Added annex.trashbin configuration.
diff --git a/COPYRIGHT b/COPYRIGHT
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -2,7 +2,7 @@
 Source: native package
 
 Files: *
-Copyright: © 2010-2025 Joey Hess <id@joeyh.name>
+Copyright: © 2010-2026 Joey Hess <id@joeyh.name>
 License: AGPL-3+
 
 Files: Assistant/WebApp.hs Assistant/WebApp/* templates/* static/*
diff --git a/CmdLine/GitRemoteAnnex.hs b/CmdLine/GitRemoteAnnex.hs
--- a/CmdLine/GitRemoteAnnex.hs
+++ b/CmdLine/GitRemoteAnnex.hs
@@ -598,8 +598,8 @@
 			specialRemoteConfig cfg
 		t <- either giveup return (SpecialRemote.findType c)
 		dummycfg <- liftIO dummyRemoteGitConfig
-		(c', u) <- Remote.setup t (Remote.Enable c) (Just (specialRemoteUUID cfg)) 
-			Nothing c dummycfg
+		(c', u) <- Remote.setup t (Remote.Enable c) (Just (specialRemoteUUID cfg))
+			remotename Nothing c dummycfg
 			`onException` cleanupremote remotename
 		Logs.Remote.configSet u c'
 		setConfig (remoteConfig c' "url") (specialRemoteUrl cfg)
diff --git a/Command/ConfigRemote.hs b/Command/ConfigRemote.hs
--- a/Command/ConfigRemote.hs
+++ b/Command/ConfigRemote.hs
@@ -55,7 +55,7 @@
 startSpecialRemote = startSpecialRemote' "configremote" . performSpecialRemote
 
 performSpecialRemote :: Annex () -> PerformSpecialRemote
-performSpecialRemote precheck _ u _ c _ mcu = do
+performSpecialRemote precheck _ u _ _ c _ mcu = do
 	precheck
 	case mcu of
 		Nothing -> Logs.Remote.configSet u c
diff --git a/Command/Dead.hs b/Command/Dead.hs
--- a/Command/Dead.hs
+++ b/Command/Dead.hs
@@ -12,7 +12,6 @@
 import Command.Trust (trustCommand)
 import Logs.Location
 import Remote (keyLocations)
-import Git.Types
 
 cmd :: Command
 cmd = withAnnexOptions [jsonOptions] $
diff --git a/Command/EnableRemote.hs b/Command/EnableRemote.hs
--- a/Command/EnableRemote.hs
+++ b/Command/EnableRemote.hs
@@ -14,7 +14,6 @@
 import qualified Logs.Remote
 import qualified Types.Remote as R
 import qualified Git
-import qualified Git.Types as Git
 import qualified Annex.SpecialRemote as SpecialRemote
 import qualified Remote
 import qualified Types.Remote as Remote
@@ -75,7 +74,7 @@
 
 -- enableremote of a normal git remote with no added parameters is a special case
 -- that retries probing the remote uuid.
-startNormalRemote :: Git.RemoteName -> Git.Repo -> CommandStart
+startNormalRemote :: RemoteName -> Git.Repo -> CommandStart
 startNormalRemote name r = starting "enableremote (normal)" ai si $ do
 	setRemoteIgnore r False
 	r' <- Remote.Git.configRead False r
@@ -85,12 +84,12 @@
 	ai = ActionItemOther (Just (UnquotedString name))
 	si = SeekInput [name]
 
-startSpecialRemote :: EnableRemoteOptions -> Git.RemoteName -> Remote.RemoteConfig -> [(UUID, Remote.RemoteConfig, Maybe (SpecialRemote.ConfigFrom UUID))] -> CommandStart
+startSpecialRemote :: EnableRemoteOptions -> RemoteName -> Remote.RemoteConfig -> [(UUID, Remote.RemoteConfig, Maybe (SpecialRemote.ConfigFrom UUID))] -> CommandStart
 startSpecialRemote o = startSpecialRemote' "enableremote" (performSpecialRemote o)
 
-type PerformSpecialRemote = RemoteType -> UUID -> R.RemoteConfig -> R.RemoteConfig -> RemoteGitConfig -> Maybe (SpecialRemote.ConfigFrom UUID) -> CommandPerform
+type PerformSpecialRemote = RemoteType -> UUID -> RemoteName -> R.RemoteConfig -> R.RemoteConfig -> RemoteGitConfig -> Maybe (SpecialRemote.ConfigFrom UUID) -> CommandPerform
 
-startSpecialRemote' :: String -> PerformSpecialRemote -> Git.RemoteName -> Remote.RemoteConfig -> [(UUID, Remote.RemoteConfig, Maybe (SpecialRemote.ConfigFrom UUID))] -> CommandStart
+startSpecialRemote' :: String -> PerformSpecialRemote -> RemoteName -> Remote.RemoteConfig -> [(UUID, Remote.RemoteConfig, Maybe (SpecialRemote.ConfigFrom UUID))] -> CommandStart
 startSpecialRemote' cname perform name config [] = do
 	m <- SpecialRemote.specialRemoteMap
 	confm <- Logs.Remote.remoteConfigMap
@@ -110,7 +109,7 @@
 		gc <- maybe (liftIO dummyRemoteGitConfig) 
 			(return . Remote.gitconfig)
 			=<< Remote.byUUID u
-		perform t u c fullconfig gc mcu
+		perform t u name c fullconfig gc mcu
   where
 	ai = ActionItemOther (Just (UnquotedString name))
 	si = SeekInput [name]
@@ -118,18 +117,15 @@
 	giveup "Multiple remotes have that name. Either use git-annex renameremote to rename them, or specify the uuid of the remote."
 
 performSpecialRemote :: EnableRemoteOptions -> PerformSpecialRemote
-performSpecialRemote o t u oldc c gc mcu = do
+performSpecialRemote o t u name oldc c gc mcu = do
 	-- Avoid enabling a special remote if there is another remote
 	-- with the same name.
-	case SpecialRemote.lookupName c of
-		Nothing -> noop
-		Just name -> do
-			rs <- Remote.remoteList
-			case filter (\rmt -> Remote.name rmt == name) rs of
-				(rmt:_) | Remote.uuid rmt /= u ->
-					giveup $ "Not overwriting currently configured git remote named \"" ++ name ++ "\""
-				_ -> noop
-	(c', u') <- R.setup t (R.Enable oldc) (Just u) Nothing c gc
+	rs <- Remote.remoteList
+	case filter (\rmt -> Remote.name rmt == name) rs of
+		(rmt:_) | Remote.uuid rmt /= u ->
+			giveup $ "Not overwriting currently configured git remote named \"" ++ name ++ "\""
+		_ -> noop
+	(c', u') <- R.setup t (R.Enable oldc) (Just u) name Nothing c gc
 	next $ cleanupSpecialRemote o t u' c' mcu
 
 cleanupSpecialRemote :: EnableRemoteOptions -> RemoteType -> UUID -> R.RemoteConfig -> Maybe (SpecialRemote.ConfigFrom UUID) -> CommandCleanup
@@ -174,7 +170,7 @@
 
 -- Use dead remote only when there is no other remote
 -- with the same name
-deadLast :: Git.RemoteName -> ([(UUID, Remote.RemoteConfig, Maybe (SpecialRemote.ConfigFrom UUID))] -> Annex a) -> Annex a
+deadLast :: RemoteName -> ([(UUID, Remote.RemoteConfig, Maybe (SpecialRemote.ConfigFrom UUID))] -> Annex a) -> Annex a
 deadLast name use =
 	SpecialRemote.findExisting' name >>= \case
 		([], l) -> use l
diff --git a/Command/Fix.hs b/Command/Fix.hs
--- a/Command/Fix.hs
+++ b/Command/Fix.hs
@@ -1,6 +1,6 @@
 {- git-annex command
  -
- - Copyright 2010-2015 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -14,6 +14,7 @@
 import qualified Annex
 import Annex.ReplaceFile
 import Annex.Content
+import Annex.Content.PointerFile
 import Annex.Perms
 import Annex.Link
 import qualified Database.Keys
@@ -28,7 +29,7 @@
 cmd :: Command
 cmd = noCommit $ withAnnexOptions [annexedMatchingOptions, jsonOptions] $
 	command "fix" SectionMaintenance
-		"fix up links to annexed content"
+		"fix up pointers to annexed content"
 		paramPaths (withParams seek)
 
 seek :: CmdParams -> CommandSeek
@@ -54,11 +55,20 @@
 				fixby $ fixSymlink file wantlink
 			| otherwise -> stop
 		Nothing -> case fixwhat of
-			FixAll -> fixthin
+			FixAll -> fixpointers
 			FixSymlinks -> stop
   where
 	file' = fromOsPath file
+
 	fixby = starting "fix" (mkActionItem (key, file)) si
+
+	fixpointers =
+		ifM (isJust <$> liftIO (isPointerFile file))
+			( stopUnless (inAnnex key) $ 
+				fixby $ fixPointerFile key file
+			, fixthin
+			)
+
 	fixthin = do
 		obj <- calcRepo (gitAnnexLocation key)
 		stopUnless (isUnmodified key file <&&> isUnmodified key obj) $ do
@@ -110,3 +120,11 @@
 	next $ return True
   where
 	link' = fromOsPath link
+
+fixPointerFile :: Key -> OsPath -> CommandPerform
+fixPointerFile key file = do
+	obj <- calcRepo (gitAnnexLocation key)
+	populatePointerFile' QueueRestage key obj file >>= \case
+		Just ic -> Database.Keys.addInodeCaches key [ic]
+		Nothing -> giveup "not enough disk space to populate pointer file"
+	next $ return True
diff --git a/Command/InitCluster.hs b/Command/InitCluster.hs
--- a/Command/InitCluster.hs
+++ b/Command/InitCluster.hs
@@ -15,7 +15,6 @@
 import Logs.UUID
 import Config
 import Annex.UUID
-import Git.Types
 import Git.Remote (isLegalName)
 
 import qualified Data.Map as M
diff --git a/Command/InitRemote.hs b/Command/InitRemote.hs
--- a/Command/InitRemote.hs
+++ b/Command/InitRemote.hs
@@ -1,6 +1,6 @@
 {- git-annex command
  -
- - Copyright 2011-2024 Joey Hess <id@joeyh.name>
+ - Copyright 2011-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -22,6 +22,7 @@
 import Config
 import Git.Config
 import Git.Types
+import Annex.Init
 
 import qualified Data.Map as M
 import qualified Data.Text as T
@@ -103,13 +104,13 @@
 	si = SeekInput (name:ws)
 	ai = ActionItemOther (Just (UnquotedString name))
 
-perform :: RemoteType -> String -> R.RemoteConfig -> InitRemoteOptions -> CommandPerform
+perform :: RemoteType -> RemoteName -> R.RemoteConfig -> InitRemoteOptions -> CommandPerform
 perform t name c o = do
 	when (privateRemote o) $
 		setConfig (remoteAnnexConfig c "private") (boolConfig True)
 	dummycfg <- liftIO dummyRemoteGitConfig
 	let c' = M.delete uuidField c
-	(c'', u) <- R.setup t R.Init (sameasu <|> uuidfromuser) Nothing c' dummycfg
+	(c'', u) <- R.setup t R.Init (sameasu <|> uuidfromuser) name Nothing c' dummycfg
 	next $ cleanup t u name c'' o
   where
 	uuidfromuser = case fromProposedAccepted <$> M.lookup uuidField c of
@@ -122,11 +123,12 @@
 uuidField :: R.RemoteConfigField
 uuidField = Accepted "uuid"
 
-cleanup :: RemoteType -> UUID -> String -> R.RemoteConfig -> InitRemoteOptions -> CommandCleanup
+cleanup :: RemoteType -> UUID -> RemoteName -> R.RemoteConfig -> InitRemoteOptions -> CommandCleanup
 cleanup t u name c o = do
 	case sameas o of
 		Nothing -> do
 			describeUUID u (toUUIDDesc name)
+			propigateInitGitConfigs u
 			Logs.Remote.configSet u c
 		Just _ -> do
 			cu <- liftIO genUUID
diff --git a/Command/List.hs b/Command/List.hs
--- a/Command/List.hs
+++ b/Command/List.hs
@@ -22,7 +22,6 @@
 import Logs.Trust
 import Logs.UUID
 import Annex.UUID
-import Git.Types (RemoteName)
 import Utility.Tuple
 import Utility.SafeOutput
 
diff --git a/Command/ReregisterUrl.hs b/Command/ReregisterUrl.hs
--- a/Command/ReregisterUrl.hs
+++ b/Command/ReregisterUrl.hs
@@ -11,7 +11,6 @@
 import Logs.Web
 import Command.FromKey (keyOpt, keyOpt')
 import qualified Remote
-import Git.Types
 
 cmd :: Command
 cmd = withAnnexOptions [jsonOptions] $ command "reregisterurl"
diff --git a/Command/TestRemote.hs b/Command/TestRemote.hs
--- a/Command/TestRemote.hs
+++ b/Command/TestRemote.hs
@@ -31,7 +31,6 @@
 import Annex.SpecialRemote.Config (exportTreeField)
 import Remote.Helper.Chunked
 import Remote.Helper.Encryptable (encryptionField, highRandomQualityField)
-import Git.Types
 import qualified Utility.FileIO as F
 import Remote.List
 
diff --git a/Command/TransferKeys.hs b/Command/TransferKeys.hs
--- a/Command/TransferKeys.hs
+++ b/Command/TransferKeys.hs
@@ -18,7 +18,6 @@
 import Annex.Transfer
 import qualified Remote
 import Utility.SimpleProtocol (dupIoHandles)
-import Git.Types (RemoteName)
 import qualified Database.Keys
 import Annex.BranchState
 
diff --git a/Database/ContentIdentifier.hs b/Database/ContentIdentifier.hs
--- a/Database/ContentIdentifier.hs
+++ b/Database/ContentIdentifier.hs
@@ -5,7 +5,6 @@
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE QuasiQuotes, TypeFamilies, TypeOperators, TemplateHaskell #-}
 {-# LANGUAGE OverloadedStrings, GADTs, FlexibleContexts, EmptyDataDecls #-}
 {-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
@@ -35,7 +34,6 @@
 import Database.Types
 import qualified Database.Queue as H
 import Database.Init
-import Database.Utility
 import Annex.Locations
 import Annex.Common hiding (delete)
 import qualified Annex.Branch
@@ -50,13 +48,7 @@
 
 import Database.Persist.Sql hiding (Key)
 import Database.Persist.TH
-
-#if MIN_VERSION_persistent_sqlite(2,13,3)
 import Database.RawFilePath
-#else
-import Database.Persist.Sqlite (runSqlite)
-import qualified Data.Text as T
-#endif
 
 data ContentIdentifierHandle = ContentIdentifierHandle H.DbQueue Bool
 
@@ -103,13 +95,8 @@
 			runMigrationSilent migrateContentIdentifier
 		-- Migrate from old versions of database, which had buggy
 		-- and suboptimal uniqueness constraints.
-#if MIN_VERSION_persistent_sqlite(2,13,3)
 		else liftIO $ runSqlite' (fromOsPath db) $ void $
 			runMigrationSilent migrateContentIdentifier
-#else
-		else liftIO $ runSqlite (T.pack (fromRawFilePath db)) $ void $
-			runMigrationSilent migrateContentIdentifier
-#endif
 	h <- liftIO $ H.openDbQueue db "content_identifiers"
 	return $ ContentIdentifierHandle h isnew
 
@@ -130,7 +117,7 @@
 -- Be sure to also update the git-annex branch when using this.
 recordContentIdentifier :: ContentIdentifierHandle -> RemoteStateHandle -> ContentIdentifier -> Key -> IO ()
 recordContentIdentifier h (RemoteStateHandle u) cid k = queueDb h $ do
-	void $ insertUniqueFast $ ContentIdentifiers u cid k
+	void $ insertUnique_ $ ContentIdentifiers u cid k
 
 getContentIdentifiers :: ContentIdentifierHandle -> RemoteStateHandle -> Key -> IO [ContentIdentifier]
 getContentIdentifiers (ContentIdentifierHandle h _) (RemoteStateHandle u) k = 
@@ -153,7 +140,7 @@
 recordAnnexBranchTree :: ContentIdentifierHandle -> Sha -> IO ()
 recordAnnexBranchTree h s = queueDb h $ do
 	deleteWhere ([] :: [Filter AnnexBranch])
-	void $ insertUniqueFast $ AnnexBranch $ toSSha s
+	void $ insertUnique_ $ AnnexBranch $ toSSha s
 
 getAnnexBranchTree :: ContentIdentifierHandle -> IO Sha
 getAnnexBranchTree (ContentIdentifierHandle h _) = H.queryDbQueue h $ do
diff --git a/Database/Export.hs b/Database/Export.hs
--- a/Database/Export.hs
+++ b/Database/Export.hs
@@ -46,7 +46,6 @@
 import Database.Types
 import qualified Database.Queue as H
 import Database.Init
-import Database.Utility
 import Annex.Locations
 import Annex.Common hiding (delete)
 import Types.Export
@@ -120,7 +119,7 @@
 recordExportTreeCurrent :: ExportHandle -> Sha -> IO ()
 recordExportTreeCurrent h s = queueDb h $ do
 	deleteWhere ([] :: [Filter ExportTreeCurrent])
-	void $ insertUniqueFast $ ExportTreeCurrent $ toSSha s
+	void $ insertUnique_ $ ExportTreeCurrent $ toSSha s
 
 getExportTreeCurrent :: ExportHandle -> IO (Maybe Sha)
 getExportTreeCurrent (ExportHandle h _) = H.queryDbQueue h $ do
@@ -132,7 +131,7 @@
 
 addExportedLocation :: ExportHandle -> Key -> ExportLocation -> IO ()
 addExportedLocation h k el = queueDb h $ do
-	void $ insertUniqueFast $ Exported k ef
+	void $ insertUnique_ $ Exported k ef
 	let edirs = map
 		(\ed -> ExportedDirectory (SByteString (fromOsPath (fromExportDirectory ed))) ef)
 		(exportDirectories el)
@@ -183,7 +182,7 @@
 
 addExportTree :: ExportHandle -> Key -> ExportLocation -> IO ()
 addExportTree h k loc = queueDb h $
-	void $ insertUniqueFast $ ExportTree k ef
+	void $ insertUnique_ $ ExportTree k ef
   where
 	ef = SByteString (fromOsPath (fromExportLocation loc))
 
diff --git a/Database/Fsck.hs b/Database/Fsck.hs
--- a/Database/Fsck.hs
+++ b/Database/Fsck.hs
@@ -27,7 +27,6 @@
 
 import Database.Types
 import qualified Database.Queue as H
-import Database.Utility
 import Database.Init
 import Annex.Locations
 import Utility.Exception
@@ -85,7 +84,7 @@
 
 addDb :: FsckHandle -> Key -> IO ()
 addDb (FsckHandle h _) k = H.queueDb h checkcommit $
-	void $ insertUniqueFast $ Fscked k
+	void $ insertUnique_ $ Fscked k
   where
 	-- Commit queue after 1000 changes or 5 minutes, whichever comes first.
 	-- The time based commit allows for an incremental fsck to be
diff --git a/Database/Handle.hs b/Database/Handle.hs
--- a/Database/Handle.hs
+++ b/Database/Handle.hs
@@ -1,6 +1,6 @@
 {- Persistent sqlite database handles.
  -
- - Copyright 2015-2023 Joey Hess <id@joeyh.name>
+ - Copyright 2015-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -24,6 +24,7 @@
 import Utility.DebugLocks
 import Utility.InodeCache
 import Utility.OsPath
+import Utility.SafeOutput
 
 import Database.Persist.Sqlite
 import qualified Database.Sqlite as Sqlite
@@ -78,7 +79,7 @@
  - it is able to run.
  -}
 queryDb :: DbHandle -> SqlPersistM a -> IO a
-queryDb (DbHandle _db _ jobs errvar) a = do
+queryDb (DbHandle db _ jobs errvar) a = do
 	res <- newEmptyMVar
 	putMVar jobs $ QueryJob $
 		debugLocks $ liftIO . putMVar res =<< tryNonAsync a
@@ -86,7 +87,7 @@
 		Right r -> either throwIO return r
 		Left BlockedIndefinitelyOnMVar -> do
 			err <- takeMVar errvar
-			giveup $ "sqlite worker thread crashed: " ++ err
+			giveup $ "sqlite worker thread for " ++ fromOsPath (safeOutput db) ++ " crashed: " ++ err
 
 {- Writes a change to the database.
  -
@@ -111,7 +112,7 @@
 					robustly a
 			Left BlockedIndefinitelyOnMVar -> do
 				err <- takeMVar errvar
-				giveup $ "sqlite worker thread crashed: " ++ err
+				giveup $ "sqlite worker thread for " ++ fromOsPath (safeOutput db) ++ " crashed: " ++ err
 	
 	briefdelay = 100000 -- 1/10th second
 
@@ -191,14 +192,10 @@
 					briefdelay
 					retryHelper "access" ex maxretries db retries ic $
 						go conn
-				| otherwise -> rethrow $ errmsg "after successful open" ex
+				| otherwise -> rethrow $ errmsg ("after successful sqlite database " ++ fromOsPath (safeOutput db) ++ " open") ex
 	
 	opensettle retries ic = do
-#if MIN_VERSION_persistent_sqlite(2,13,3)
 		conn <- Sqlite.open' (fromOsPath db)
-#else
-		conn <- Sqlite.open (T.pack (fromOsPath db))
-#endif
 		settle conn retries ic
 
 	settle conn retries ic = do
@@ -217,7 +214,7 @@
 						if e == Sqlite.ErrorIO
 							then opensettle
 							else settle conn
-				| otherwise -> rethrow $ errmsg "while opening database connection" ex
+				| otherwise -> rethrow $ errmsg ("while opening sqlite database " ++ fromOsPath (safeOutput db) ++ " connection") ex
 	
 	-- This should succeed for any table.
 	nullselect = T.pack $ "SELECT null from " ++ tablename ++ " limit 1"
@@ -274,7 +271,7 @@
 				| e == Sqlite.ErrorBusy -> do
 					threadDelay briefdelay
 					retryHelper "close" ex maxretries db retries ic go
-				| otherwise -> rethrow $ errmsg "while closing database connection" ex
+				| otherwise -> rethrow $ errmsg ("while closing sqlite database " ++ fromOsPath (safeOutput db) ++ " connection") ex
 	
 	briefdelay = 1000 -- 1/1000th second
 	
@@ -312,7 +309,7 @@
 
 databaseAccessStalledMsg :: Show err => String -> OsPath -> err -> String
 databaseAccessStalledMsg action db err =
-	"Repeatedly unable to " ++ action ++ " sqlite database " ++ fromOsPath db 
+	"Repeatedly unable to " ++ action ++ " sqlite database " ++ fromOsPath (safeOutput db)
 		++ ": " ++ show err ++ ". "
 		++ "Perhaps another git-annex process is suspended and is "
 		++ "keeping this database locked?"
diff --git a/Database/ImportFeed.hs b/Database/ImportFeed.hs
--- a/Database/ImportFeed.hs
+++ b/Database/ImportFeed.hs
@@ -26,7 +26,6 @@
 import Database.Types
 import qualified Database.Queue as H
 import Database.Init
-import Database.Utility
 import Annex.Locations
 import Annex.Common hiding (delete)
 import qualified Annex.Branch
@@ -109,16 +108,16 @@
 
 recordKnownUrl :: ImportFeedDbHandle -> URLByteString -> IO ()
 recordKnownUrl h u = queueDb h $
-	void $ insertUniqueFast $ KnownUrls $ SByteString u
+	void $ insertUnique_ $ KnownUrls $ SByteString u
 
 recordKnownItemId :: ImportFeedDbHandle -> SByteString -> IO ()
 recordKnownItemId h i = queueDb h $
-	void $ insertUniqueFast $ KnownItemIds i
+	void $ insertUnique_ $ KnownItemIds i
 
 recordAnnexBranchTree :: ImportFeedDbHandle -> Sha -> IO ()
 recordAnnexBranchTree h s = queueDb h $ do
 	deleteWhere ([] :: [Filter AnnexBranch])
-	void $ insertUniqueFast $ AnnexBranch $ toSSha s
+	void $ insertUnique_ $ AnnexBranch $ toSSha s
 
 getAnnexBranchTree :: ImportFeedDbHandle -> IO Sha
 getAnnexBranchTree (ImportFeedDbHandle h) = H.queryDbQueue h $ do
diff --git a/Database/Init.hs b/Database/Init.hs
--- a/Database/Init.hs
+++ b/Database/Init.hs
@@ -5,7 +5,7 @@
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
-{-# LANGUAGE OverloadedStrings, CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Database.Init where
 
@@ -13,9 +13,7 @@
 import Annex.Perms
 import Utility.FileMode
 import qualified Utility.RawFilePath as R
-#if MIN_VERSION_persistent_sqlite(2,13,3)
 import Database.RawFilePath
-#endif
 
 import Database.Persist.Sqlite
 import Lens.Micro
@@ -36,11 +34,7 @@
 	let tmpdb = tmpdbdir </> literalOsPath "db"
 	let tmpdb' = fromOsPath tmpdb
 	createAnnexDirectory tmpdbdir
-#if MIN_VERSION_persistent_sqlite(2,13,3)
 	liftIO $ runSqliteInfo' tmpdb' (enableWAL tmpdb) migration
-#else
-	liftIO $ runSqliteInfo (enableWAL tmpdb) migration
-#endif
 	setAnnexDirPerm tmpdbdir
 	-- Work around sqlite bug that prevents it from honoring
 	-- less restrictive umasks.
diff --git a/Database/Keys/SQL.hs b/Database/Keys/SQL.hs
--- a/Database/Keys/SQL.hs
+++ b/Database/Keys/SQL.hs
@@ -18,7 +18,6 @@
 
 import Database.Types
 import Database.Handle
-import Database.Utility
 import qualified Database.Queue as H
 import Utility.InodeCache
 import Git.FilePath
@@ -120,7 +119,7 @@
 
 addInodeCaches :: Key -> [InodeCache] -> WriteHandle -> IO ()
 addInodeCaches k is = queueDb $
-	forM_ is $ \i -> insertUniqueFast $ Content k i 
+	forM_ is $ \i -> insertUnique_ $ Content k i 
 		(inodeCacheToFileSize i)
 		(inodeCacheToEpochTime i)
 
diff --git a/Database/RawFilePath.hs b/Database/RawFilePath.hs
--- a/Database/RawFilePath.hs
+++ b/Database/RawFilePath.hs
@@ -31,11 +31,10 @@
  - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 -}
 
-{-# LANGUAGE OverloadedStrings, CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Database.RawFilePath where
 
-#if MIN_VERSION_persistent_sqlite(2,13,3)
 import Database.Persist.Sqlite
 import qualified Database.Sqlite as Sqlite
 import Utility.RawFilePath (RawFilePath)
@@ -92,4 +91,3 @@
 	-> (SqlBackend -> m a)
 	-> m a
 withSqliteConnInfo' db = withSqlConn . openWith' db const
-#endif
diff --git a/Database/RepoSize.hs b/Database/RepoSize.hs
--- a/Database/RepoSize.hs
+++ b/Database/RepoSize.hs
@@ -38,7 +38,6 @@
 import Database.RepoSize.Handle
 import qualified Database.Handle as H
 import Database.Init
-import Database.Utility
 import Database.Types
 import Annex.LockFile
 import Git.Types
@@ -200,7 +199,7 @@
 recordAnnexBranchCommit :: Sha -> SqlPersistM ()
 recordAnnexBranchCommit branchcommitsha = do
 	deleteWhere ([] :: [Filter AnnexBranch])
-	void $ insertUniqueFast $ AnnexBranch $ toSSha branchcommitsha
+	void $ insertUnique_ $ AnnexBranch $ toSSha branchcommitsha
 
 startingLiveSizeChange :: RepoSizeHandle -> UUID -> Key -> SizeChange -> SizeChangeId -> IO ()
 startingLiveSizeChange (RepoSizeHandle (Just h) _) u k sc cid = 
diff --git a/Database/Utility.hs b/Database/Utility.hs
deleted file mode 100644
--- a/Database/Utility.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-{- Persistent sqlite database utilities.
- -
- - Copyright 2023 Joey Hess <id@joeyh.name>
- -
- - Licensed under the GNU AGPL version 3 or higher.
- -}
-
-{-# LANGUAGE TypeFamilies, CPP #-}
-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
-
-module Database.Utility (
-	insertUniqueFast,
-) where
-
-import Control.Monad
-import Database.Persist.Class
-
-{- insertUnique_ is 2x as fast as insertUnique, so use when available.
- -
- - It would be difficult to write the type signature here, since older
- - versions of persistent have different constraints on insertUnique.
- -}
-#if MIN_VERSION_persistent(2,14,5)
-insertUniqueFast x = void (insertUnique_ x)
-#else
-insertUniqueFast x = void (insertUnique x)
-#endif
diff --git a/Git/Credential.hs b/Git/Credential.hs
--- a/Git/Credential.hs
+++ b/Git/Credential.hs
@@ -1,6 +1,6 @@
 {- git credential interface
  -
- - Copyright 2019-2022 Joey Hess <id@joeyh.name>
+ - Copyright 2019-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -19,6 +19,8 @@
 
 import qualified Data.Map as M
 import Network.URI
+import Network.HTTP.Types
+import Network.HTTP.Types.Header
 import Control.Concurrent.STM
 
 data Credential = Credential { fromCredential :: M.Map String String }
@@ -35,7 +37,7 @@
 	<*> credentialPassword cred
 
 getBasicAuthFromCredential :: Repo -> TMVar CredentialCache -> GetBasicAuth
-getBasicAuthFromCredential r ccv u = do
+getBasicAuthFromCredential r ccv u respheaders = do
 	(CredentialCache cc) <- atomically $ readTMVar ccv
 	case mkCredentialBaseURL r u of
 		Just bu -> case M.lookup bu cc of
@@ -44,8 +46,8 @@
 				let storeincache = \c -> atomically $ do
 					CredentialCache cc' <- takeTMVar ccv
 					putTMVar ccv (CredentialCache (M.insert bu c cc'))
-				go storeincache =<< getUrlCredential u r
-		Nothing -> go (const noop) =<< getUrlCredential u r
+				go storeincache =<< getUrlCredential u respheaders r
+		Nothing -> go (const noop) =<< getUrlCredential u respheaders r
   where
 	go storeincache c =
 		case credentialBasicAuth c of
@@ -61,8 +63,9 @@
 
 -- | This may prompt the user for the credential, or get a cached
 -- credential from git.
-getUrlCredential :: URLString -> Repo -> IO Credential
-getUrlCredential = runCredential "fill" . urlCredential
+getUrlCredential :: URLString -> ResponseHeaders -> Repo -> IO Credential
+getUrlCredential url respheaders = runCredential "fill" $ 
+	urlCredential url respheaders
 
 -- | Call if the credential the user entered works, and can be cached for
 -- later use if git is configured to do so.
@@ -73,8 +76,12 @@
 rejectUrlCredential :: Credential -> Repo -> IO ()
 rejectUrlCredential c = void . runCredential "reject" c
 
-urlCredential :: URLString -> Credential
-urlCredential = Credential . M.singleton "url"
+urlCredential :: URLString -> ResponseHeaders -> Credential
+urlCredential url respheaders = Credential $ M.fromList $
+	("url", url) : map wwwauth (filter iswwwauth respheaders)
+  where
+	iswwwauth (h, _) = h == hWWWAuthenticate
+	wwwauth (_, v) = ("wwwauth[]", decodeBS v)
 
 runCredential :: String -> Credential -> Repo -> IO Credential
 runCredential action input r =
diff --git a/Logs/Proxy.hs b/Logs/Proxy.hs
--- a/Logs/Proxy.hs
+++ b/Logs/Proxy.hs
@@ -17,7 +17,6 @@
 import Annex.Common
 import qualified Annex.Branch
 import qualified Git.Remote
-import Git.Types
 import Logs
 import Logs.UUIDBased
 import Logs.MapLog
diff --git a/P2P/Http/Client.hs b/P2P/Http/Client.hs
--- a/P2P/Http/Client.hs
+++ b/P2P/Http/Client.hs
@@ -2,7 +2,7 @@
  -
  - https://git-annex.branchable.com/design/p2p_protocol_over_http/
  -
- - Copyright 2024 Joey Hess <id@joeyh.name>
+ - Copyright 2024-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -35,7 +35,6 @@
 import Annex.Concurrent
 import Utility.Url (BasicAuth(..))
 import Utility.HumanTime
-import Utility.STM
 import qualified Utility.FileIO as F
 import qualified Git.Credential as Git
 
@@ -43,15 +42,17 @@
 import Servant.Client.Streaming
 import qualified Servant.Types.SourceT as S
 import Network.HTTP.Types.Status
-import Network.HTTP.Client
+import Network.HTTP.Client hiding (responseHeaders)
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Lazy.Internal as LI
 import qualified Data.Map as M
 import Control.Concurrent.Async
+import Control.Concurrent.STM
 import Control.Concurrent
 import System.IO.Unsafe
 import Data.Time.Clock.POSIX
 import qualified Data.ByteString.Lazy as L
+import Data.Foldable (toList)
 
 type ClientAction a
 	= ClientEnv
@@ -119,7 +120,7 @@
 					go clientenv mcred credcached mauth vs
 				| statusCode (responseStatusCode resp) == 401 ->
 					case mcred of
-						Nothing -> authrequired clientenv (v:vs)
+						Nothing -> authrequired clientenv resp (v:vs)
 						Just cred -> do
 							inRepo $ Git.rejectUrlCredential cred
 							Just <$> fallback (showstatuscode resp)
@@ -134,9 +135,10 @@
 
 	catchclienterror a = a `catch` \(ex :: ClientError) -> pure (Left ex)
 
-	authrequired clientenv vs = do
+	authrequired clientenv resp vs = do
+		let respheaders = toList $ responseHeaders resp
 		cred <- prompt $ 
-			inRepo $ Git.getUrlCredential credentialbaseurl
+			inRepo $ Git.getUrlCredential credentialbaseurl respheaders
 		go clientenv (Just cred) False (credauth cred) vs
 
 	showstatuscode resp = 
diff --git a/P2P/Http/Server.hs b/P2P/Http/Server.hs
--- a/P2P/Http/Server.hs
+++ b/P2P/Http/Server.hs
@@ -34,7 +34,6 @@
 import Remote.List
 import Types.Direction
 import Utility.Metered
-import Utility.STM
 
 import Servant
 import qualified Servant.Types.SourceT as S
@@ -42,6 +41,7 @@
 import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString.Lazy.Internal as LI
 import Control.Concurrent.Async
+import Control.Concurrent.STM
 import Control.Concurrent
 import System.IO.Unsafe
 import Data.Either
diff --git a/P2P/Http/State.hs b/P2P/Http/State.hs
--- a/P2P/Http/State.hs
+++ b/P2P/Http/State.hs
@@ -38,13 +38,13 @@
 import Annex.Cluster
 import qualified P2P.Proxy as Proxy
 import qualified Types.Remote as Remote
-import Utility.STM
 import Remote.List
 
 import Servant
 import qualified Data.Map.Strict as M
 import qualified Data.Set as S
 import Control.Concurrent.Async
+import Control.Concurrent.STM
 import Data.Time.Clock.POSIX
 
 data P2PHttpServerState = P2PHttpServerState
diff --git a/P2P/Http/Types.hs b/P2P/Http/Types.hs
--- a/P2P/Http/Types.hs
+++ b/P2P/Http/Types.hs
@@ -11,7 +11,6 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE DataKinds #-}
-{-# LANGUAGE CPP #-}
 
 module P2P.Http.Types where
 
@@ -149,11 +148,7 @@
 
 instance ToHttpApiData Auth where
 	toHeader (Auth u p) = "Basic " <> B64.encode (u <> ":" <> p)
-#if MIN_VERSION_text(2,0,0)
 	toUrlPiece = TE.decodeUtf8Lenient . toHeader
-#else
-	toUrlPiece = TE.decodeUtf8With (\_ _ -> Just '\xfffd') . toHeader
-#endif
 
 instance FromHttpApiData Auth where
 	parseHeader h =
diff --git a/Remote.hs b/Remote.hs
--- a/Remote.hs
+++ b/Remote.hs
@@ -85,7 +85,7 @@
 import Remote.List.Util
 import Config
 import Config.DynamicConfig
-import Git.Types (RemoteName, ConfigKey(..), fromConfigValue)
+import Git.Types (ConfigKey(..), fromConfigValue)
 import Utility.Aeson
 
 {- Map from UUIDs of Remotes to a calculated value. -}
diff --git a/Remote/Adb.hs b/Remote/Adb.hs
--- a/Remote/Adb.hs
+++ b/Remote/Adb.hs
@@ -139,8 +139,8 @@
 	serial = maybe (giveup "missing androidserial") AndroidSerial
 		(remoteAnnexAndroidSerial gc)
 
-adbSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-adbSetup ss mu _ c gc = do
+adbSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+adbSetup ss mu _ _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 
 	-- verify configuration
diff --git a/Remote/Borg.hs b/Remote/Borg.hs
--- a/Remote/Borg.hs
+++ b/Remote/Borg.hs
@@ -133,8 +133,8 @@
 		BorgRepo
 		(remoteAnnexBorgRepo gc)
 
-borgSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-borgSetup _ mu _ c _gc = do
+borgSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+borgSetup _ mu _ _ c _gc = do
 	u <- maybe (liftIO genUUID) return mu
 
 	-- verify configuration is sane
diff --git a/Remote/Bup.hs b/Remote/Bup.hs
--- a/Remote/Bup.hs
+++ b/Remote/Bup.hs
@@ -123,8 +123,8 @@
   where
 	buprepo = fromMaybe (giveup "missing buprepo") $ remoteAnnexBupRepo gc
 
-bupSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-bupSetup ss mu _ c gc = do
+bupSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+bupSetup ss mu _ _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 
 	-- verify configuration is sane
diff --git a/Remote/Compute.hs b/Remote/Compute.hs
--- a/Remote/Compute.hs
+++ b/Remote/Compute.hs
@@ -139,8 +139,8 @@
 		, remoteStateHandle = rs
 		}
 
-setupInstance :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-setupInstance ss mu _ c _ = do
+setupInstance :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+setupInstance ss mu _ _ c _ = do
 	ComputeProgram program <- either giveup return $ getComputeProgram' c
 	allowedprograms <- maybe [] words . annexAllowedComputePrograms
 		<$> Annex.getGitConfig
diff --git a/Remote/Ddar.hs b/Remote/Ddar.hs
--- a/Remote/Ddar.hs
+++ b/Remote/Ddar.hs
@@ -114,8 +114,8 @@
 		}
 	ddarrepo = maybe (giveup "missing ddarrepo") (DdarRepo gc) (remoteAnnexDdarRepo gc)
 
-ddarSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-ddarSetup ss mu _ c gc = do
+ddarSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+ddarSetup ss mu _ _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 
 	-- verify configuration is sane
diff --git a/Remote/Directory.hs b/Remote/Directory.hs
--- a/Remote/Directory.hs
+++ b/Remote/Directory.hs
@@ -150,8 +150,8 @@
 	dir' = fromMaybe (giveup "missing directory")
 		(remoteAnnexDirectory gc)
 
-directorySetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-directorySetup ss mu _ c gc = do
+directorySetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+directorySetup ss mu _ _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 	-- verify configuration is sane
 	let dir = maybe (giveup "Specify directory=") fromProposedAccepted $
diff --git a/Remote/External.hs b/Remote/External.hs
--- a/Remote/External.hs
+++ b/Remote/External.hs
@@ -1,6 +1,6 @@
 {- External special remote interface.
  -
- - Copyright 2013-2025 Joey Hess <id@joeyh.name>
+ - Copyright 2013-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -171,8 +171,8 @@
 			fromMaybe (giveup "missing externaltype")
 				(remoteAnnexExternalType gc)
 
-externalSetup :: Maybe ExternalProgram -> Maybe (String, String) -> SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-externalSetup externalprogram setgitconfig ss mu _ c gc = do
+externalSetup :: Maybe ExternalProgram -> Maybe (String, String) -> SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+externalSetup externalprogram setgitconfig ss mu remotename _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 	pc <- either giveup return $ parseRemoteConfig c (lenientRemoteConfigParser externalprogram)
 	let readonlyconfig = getRemoteConfigValue readonlyField pc == Just True
@@ -193,7 +193,7 @@
 		else do
 			pc' <- either giveup return $ parseRemoteConfig c' (lenientRemoteConfigParser externalprogram)
 			let p = fromMaybe (ExternalType externaltype) externalprogram
-			external <- newExternal p (Just u) pc' (Just gc) Nothing Nothing
+			external <- newExternal p (Just u) pc' (Just gc) (Just remotename) Nothing
 			-- Now that we have an external, ask it to LISTCONFIGS, 
 			-- and re-parse the RemoteConfig strictly, so we can
 			-- error out if the user provided an unexpected config.
@@ -953,3 +953,4 @@
   where
 	isproposed (Accepted _) = False
 	isproposed (Proposed _) = True
+
diff --git a/Remote/External/Types.hs b/Remote/External/Types.hs
--- a/Remote/External/Types.hs
+++ b/Remote/External/Types.hs
@@ -52,7 +52,6 @@
 import Types.Export
 import Types.Availability (Availability(..))
 import Types.Key
-import Git.Types
 import Utility.Url (URLString)
 import Utility.Url.Parse
 import qualified Utility.SimpleProtocol as Proto
diff --git a/Remote/GCrypt.hs b/Remote/GCrypt.hs
--- a/Remote/GCrypt.hs
+++ b/Remote/GCrypt.hs
@@ -219,8 +219,8 @@
 unsupportedUrl :: a
 unsupportedUrl = giveup "unsupported repo url for gcrypt"
 
-gCryptSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-gCryptSetup ss mu _ c gc = go $ fromProposedAccepted <$> M.lookup gitRepoField c
+gCryptSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+gCryptSetup ss mu _ _ c gc = go $ fromProposedAccepted <$> M.lookup gitRepoField c
   where
 	remotename = fromJust (lookupName c)
 	go Nothing = giveup "Specify gitrepo="
diff --git a/Remote/Git.hs b/Remote/Git.hs
--- a/Remote/Git.hs
+++ b/Remote/Git.hs
@@ -141,8 +141,8 @@
  - No attempt is made to make the remote be accessible via ssh key setup,
  - etc.
  -}
-gitSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-gitSetup Init mu _ c gc = do
+gitSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+gitSetup Init mu _ _ c gc = do
 	let location = maybe (giveup "Specify location=url") fromProposedAccepted $
 		M.lookup locationField c
 	r <- inRepo $ Git.Construct.fromRemoteLocation location False
@@ -153,8 +153,8 @@
 		else if isNothing mu || mu == Just u
 			then enableRemote (Just u) c
 			else giveup "git repository does not have specified uuid"
-gitSetup (Enable _) mu _ c _ = enableRemote mu c
-gitSetup (AutoEnable _) mu _ c _ = enableRemote mu c
+gitSetup (Enable _) mu _ _ c _ = enableRemote mu c
+gitSetup (AutoEnable _) mu _ _ c _ = enableRemote mu c
 
 enableRemote :: Maybe UUID -> RemoteConfig -> Annex (RemoteConfig, UUID)
 enableRemote (Just u) c = do
diff --git a/Remote/GitLFS.hs b/Remote/GitLFS.hs
--- a/Remote/GitLFS.hs
+++ b/Remote/GitLFS.hs
@@ -144,8 +144,8 @@
 		, remoteStateHandle = rs
 		}
 
-mySetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-mySetup ss mu _ c gc = do
+mySetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+mySetup ss mu _ _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 
 	(c', _encsetup) <- encryptionSetup ss c gc
@@ -316,7 +316,10 @@
 			resp <- makeSmallAPIRequest testreq
 			if needauth (responseStatus resp)
 				then do
-					cred <- prompt $ inRepo $ Git.getUrlCredential (show lfsrepouri)
+					cred <- prompt $ inRepo $
+						Git.getUrlCredential
+							(show lfsrepouri)
+							(responseHeaders resp)
 					let endpoint' = addbasicauth (Git.credentialBasicAuth cred) endpoint
 					let testreq' = LFS.startTransferRequest endpoint' transfernothing
 					flip catchNonAsync (const (returnendpoint endpoint')) $ do
diff --git a/Remote/Glacier.hs b/Remote/Glacier.hs
--- a/Remote/Glacier.hs
+++ b/Remote/Glacier.hs
@@ -118,8 +118,8 @@
 			{ chunkConfig = NoChunks
 			}
 
-glacierSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-glacierSetup ss mu mcreds c gc = do
+glacierSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+glacierSetup ss mu _ mcreds c gc = do
 	u <- maybe (liftIO genUUID) return mu
 	glacierSetup' ss u mcreds c gc
 glacierSetup' :: SetupStage -> UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
diff --git a/Remote/Helper/ExportImport.hs b/Remote/Helper/ExportImport.hs
--- a/Remote/Helper/ExportImport.hs
+++ b/Remote/Helper/ExportImport.hs
@@ -79,7 +79,7 @@
 adjustExportImportRemoteType :: RemoteType -> RemoteType
 adjustExportImportRemoteType rt = rt { setup = setup' }
   where
-	setup' st mu cp c gc = do
+	setup' st mu remotename cp c gc = do
 		pc <- either giveup return . parseRemoteConfig c
 			=<< configParser rt c
 		let checkconfig supported configured configfield cont =
@@ -97,7 +97,7 @@
 				)
 		checkconfig exportSupported exportTree exportTreeField $
 			checkconfig importSupported importTree importTreeField $
-				setup rt st mu cp c gc
+				setup rt st mu remotename cp c gc
 	
 	enable oldc pc configured configfield cont = do
 		oldpc <- parsedRemoteConfig rt oldc
diff --git a/Remote/Hook.hs b/Remote/Hook.hs
--- a/Remote/Hook.hs
+++ b/Remote/Hook.hs
@@ -96,8 +96,8 @@
   where
 	hooktype = fromMaybe (giveup "missing hooktype") $ remoteAnnexHookType gc
 
-hookSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-hookSetup ss mu _ c gc = do
+hookSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+hookSetup ss mu _ _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 	let hooktype = maybe (giveup "Specify hooktype=") fromProposedAccepted $
 		M.lookup hooktypeField c
diff --git a/Remote/HttpAlso.hs b/Remote/HttpAlso.hs
--- a/Remote/HttpAlso.hs
+++ b/Remote/HttpAlso.hs
@@ -108,10 +108,10 @@
 cannotModify :: a
 cannotModify = giveup "httpalso special remote is read only"
 
-httpAlsoSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-httpAlsoSetup _ Nothing _ _ _ =
+httpAlsoSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+httpAlsoSetup _ Nothing _ _ _ _ =
 	giveup "Must use --sameas when initializing a httpalso remote."
-httpAlsoSetup ss (Just u) _ c gc = do
+httpAlsoSetup ss (Just u) _ _ c gc = do
 	_url <- maybe (giveup "Specify url=")
 		(return . fromProposedAccepted)
 		(M.lookup urlField c)
diff --git a/Remote/Mask.hs b/Remote/Mask.hs
--- a/Remote/Mask.hs
+++ b/Remote/Mask.hs
@@ -89,8 +89,8 @@
 		(checkKey maskedremote)
 		this
 
-maskSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-maskSetup setupstage mu _ c gc = do
+maskSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+maskSetup setupstage mu _ _ c gc = do
 	remotelist <- Annex.getState Annex.remotes
 	let findnamed maskremotename =
 		case filter (\r -> name r == maskremotename) remotelist of
diff --git a/Remote/Rsync.hs b/Remote/Rsync.hs
--- a/Remote/Rsync.hs
+++ b/Remote/Rsync.hs
@@ -199,8 +199,8 @@
 	loginopt = maybe [] (\l -> ["-l",l]) login
 	fromNull as xs = if null xs then as else xs
 
-rsyncSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-rsyncSetup ss mu _ c gc = do
+rsyncSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+rsyncSetup ss mu _ _ c gc = do
 	u <- maybe (liftIO genUUID) return mu
 	-- verify configuration is sane
 	let url = maybe (giveup "Specify rsyncurl=") fromProposedAccepted $
diff --git a/Remote/S3.hs b/Remote/S3.hs
--- a/Remote/S3.hs
+++ b/Remote/S3.hs
@@ -270,8 +270,8 @@
 			, remoteStateHandle = rs
 			}
 
-s3Setup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-s3Setup ss mu mcreds c gc = do
+s3Setup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+s3Setup ss mu _ mcreds c gc = do
 	u <- maybe (liftIO genUUID) return mu
 	s3Setup' ss u mcreds c gc
 
diff --git a/Remote/Tahoe.hs b/Remote/Tahoe.hs
--- a/Remote/Tahoe.hs
+++ b/Remote/Tahoe.hs
@@ -125,8 +125,8 @@
 		, remoteStateHandle = rs
 		}
 
-tahoeSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-tahoeSetup _ mu _ c _ = do
+tahoeSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+tahoeSetup _ mu _ _ c _ = do
 	furl <- maybe (fromMaybe missingfurl $ M.lookup furlField c) Proposed
 		<$> liftIO (getEnv "TAHOE_FURL")
 	u <- maybe (liftIO genUUID) return mu
diff --git a/Remote/Web.hs b/Remote/Web.hs
--- a/Remote/Web.hs
+++ b/Remote/Web.hs
@@ -109,8 +109,8 @@
 		, remoteStateHandle = rs
 		}
 
-setupInstance :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-setupInstance _ mu _ c _ = do
+setupInstance :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+setupInstance _ mu _ _ c _ = do
 	u <- maybe (liftIO genUUID) return mu
 	gitConfigSpecialRemote u c [("web", "true")]
 	return (c, u)
diff --git a/Remote/WebDAV.hs b/Remote/WebDAV.hs
--- a/Remote/WebDAV.hs
+++ b/Remote/WebDAV.hs
@@ -128,8 +128,8 @@
 			}
 		chunkconfig = getChunkConfig c
 
-webdavSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
-webdavSetup ss mu mcreds c gc = do
+webdavSetup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)
+webdavSetup ss mu _ mcreds c gc = do
 	u <- maybe (liftIO genUUID) return mu
 	url <- maybe (giveup "Specify url=")
 		(return . fromProposedAccepted)
diff --git a/Types/Cluster.hs b/Types/Cluster.hs
--- a/Types/Cluster.hs
+++ b/Types/Cluster.hs
@@ -5,7 +5,7 @@
  - Licensed under the GNU AGPL version 3 or higher.
  -}
 
-{-# LANGUAGE CPP, OverloadedStrings #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Types.Cluster (
 	ClusterUUID,
@@ -43,12 +43,7 @@
 -- Check if it is a valid cluster UUID.
 isClusterUUID :: UUID -> Bool
 isClusterUUID (UUID b) 
-	| B.take 2 b == "ac" = 
-#if MIN_VERSION_bytestring(0,11,0)
-		B.indexMaybe b 14 == Just eight
-#else
-		B.length b > 14 && B.head (B.drop 14 b) == eight
-#endif
+	| B.take 2 b == "ac" = B.indexMaybe b 14 == Just eight
   where
 	eight = fromIntegral (ord '8')
 isClusterUUID _ = False
diff --git a/Types/GitConfig.hs b/Types/GitConfig.hs
--- a/Types/GitConfig.hs
+++ b/Types/GitConfig.hs
@@ -1,6 +1,6 @@
 {- git-annex configuration
  -
- - Copyright 2012-2025 Joey Hess <id@joeyh.name>
+ - Copyright 2012-2026 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -51,6 +51,7 @@
 import Types.StallDetection
 import Types.View
 import Types.Cluster
+import Types.Group
 import Config.DynamicConfig
 import Utility.HumanTime
 import Utility.Gpg (GpgCmd, mkGpgCmd)
@@ -171,6 +172,9 @@
 	, annexViewUnsetDirectory :: ViewUnset
 	, annexClusters :: M.Map RemoteName ClusterUUID
 	, annexFullyBalancedThreshhold :: Double
+	, annexInitWanted :: Maybe String
+	, annexInitRequired :: Maybe String
+	, annexInitGroups :: [Group]
 	}
 
 extractGitConfig :: ConfigSource -> Git.Repo -> GitConfig
@@ -284,7 +288,7 @@
 		(getmayberead (annexConfig "adjustedbranchrefresh"))
 	, annexSupportUnlocked = getbool (annexConfig "supportunlocked") True
 	, annexAssistantAllowUnlocked = getbool (annexConfig "assistant.allowunlocked") False
-	, annexTrashbin = getmaybe "annex.trashbin"
+	, annexTrashbin = getmaybe (annexConfig "trashbin")
 	, coreSymlinks = getbool "core.symlinks" True
 	, coreSharedRepository = getSharedRepository r
 	, coreQuotePath = QuotePath (getbool "core.quotepath" True)
@@ -315,6 +319,10 @@
 	, annexFullyBalancedThreshhold =
 		fromMaybe 0.9 $ (/ 100) <$> getmayberead
 			(annexConfig "fullybalancedthreshhold")
+	, annexInitWanted = getmaybe (annexConfig "initwanted")
+	, annexInitRequired = getmaybe (annexConfig "initrequired")
+	, annexInitGroups = map (Group . encodeBS) $
+		getwords (annexConfig "initgroups")
 	}
   where
 	getbool k d = fromMaybe d $ getmaybebool k
diff --git a/Types/Remote.hs b/Types/Remote.hs
--- a/Types/Remote.hs
+++ b/Types/Remote.hs
@@ -65,7 +65,7 @@
 	-- parse configs of remotes of this type
 	, configParser :: RemoteConfig -> a RemoteConfigParser
 	-- initializes or enables a remote
-	, setup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> a (RemoteConfig, UUID)
+	, setup :: SetupStage -> Maybe UUID -> RemoteName -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> a (RemoteConfig, UUID)
 	-- check if a remote of this type is able to support export
 	, exportSupported :: ParsedRemoteConfig -> RemoteGitConfig -> a Bool
 	-- check if a remote of this type is able to support import
diff --git a/Types/Transferrer.hs b/Types/Transferrer.hs
--- a/Types/Transferrer.hs
+++ b/Types/Transferrer.hs
@@ -9,7 +9,6 @@
 
 import Annex.Common
 import Types.Messages
-import Git.Types (RemoteName)
 import qualified Utility.SimpleProtocol as Proto
 import Utility.Format
 import Utility.Metered (TotalSize(..))
diff --git a/Utility/LockFile/Windows.hs b/Utility/LockFile/Windows.hs
--- a/Utility/LockFile/Windows.hs
+++ b/Utility/LockFile/Windows.hs
@@ -5,7 +5,7 @@
  - License: BSD-2-clause
  -}
 
-{-# LANGUAGE OverloadedStrings, CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
 
 module Utility.LockFile.Windows (
 	lockShared,
@@ -22,9 +22,7 @@
 import Utility.Path.Windows
 import Utility.FileSystemEncoding
 import Utility.OsPath
-#if MIN_VERSION_Win32(2,13,4)
 import Common (tryNonAsync)
-#endif
 
 type LockFile = OsPath
 
@@ -62,21 +60,12 @@
 openLock :: ShareMode -> LockFile -> IO (Maybe LockHandle)
 openLock sharemode f = do
 	f' <- convertToWindowsNativeNamespace (fromOsPath f)
-#if MIN_VERSION_Win32(2,13,4)
 	r <- tryNonAsync $ createFile_NoRetry (fromRawFilePath f') gENERIC_READ sharemode 
 		Nothing oPEN_ALWAYS fILE_ATTRIBUTE_NORMAL
 		Nothing
 	return $ case r of
 		Left _ -> Nothing
 		Right h -> Just h
-#else
-	h <- withTString (fromRawFilePath f') $ \c_f ->
-		c_CreateFile c_f gENERIC_READ sharemode (maybePtr Nothing)
-			oPEN_ALWAYS fILE_ATTRIBUTE_NORMAL (maybePtr Nothing)
-	return $ if h == iNVALID_HANDLE_VALUE
-		then Nothing
-		else Just h
-#endif
 
 dropLock :: LockHandle -> IO ()
 dropLock = closeHandle
diff --git a/Utility/OpenFd.hs b/Utility/OpenFd.hs
--- a/Utility/OpenFd.hs
+++ b/Utility/OpenFd.hs
@@ -1,4 +1,4 @@
-{- openFd wrapper to support old versions of unix package.
+{- openFd wrapper
  -
  - Copyright 2023-2025 Joey Hess <id@joeyh.name>
  -
@@ -14,9 +14,6 @@
 
 import System.Posix.IO.ByteString
 import System.Posix.Types
-#if ! MIN_VERSION_unix(2,8,0)
-import Control.Monad
-#endif
 
 import Utility.RawFilePath
 
@@ -24,13 +21,6 @@
 
 openFdWithMode :: RawFilePath -> OpenMode -> Maybe FileMode -> OpenFileFlags -> CloseOnExecFlag -> IO Fd
 openFdWithMode f openmode filemode flags (CloseOnExecFlag closeonexec) = do
-#if MIN_VERSION_unix(2,8,0)
 	openFd f openmode (flags { creat = filemode, cloexec = closeonexec })
-#else
-	fd <- openFd f openmode filemode flags
-	when closeonexec $
-		setFdOption fd CloseOnExec True
-	return fd
-#endif
 
 #endif
diff --git a/Utility/STM.hs b/Utility/STM.hs
deleted file mode 100644
--- a/Utility/STM.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-{- support for old versions of the stm package
- -
- - Copyright 2024 Joey Hess <id@joeyh.name>
- -
- - License: BSD-2-clause
- -}
-
-{-# LANGUAGE CPP #-}
-{-# OPTIONS_GHC -fno-warn-tabs #-}
-
-module Utility.STM (
-	module Control.Concurrent.STM,
-#if ! MIN_VERSION_stm(2,5,1)
-	writeTMVar
-#endif
-) where
-
-import Control.Concurrent.STM
-
-#if ! MIN_VERSION_stm(2,5,1)
-writeTMVar :: TMVar t -> t -> STM ()
-writeTMVar t new = tryTakeTMVar t >> putTMVar t new
-#endif
diff --git a/Utility/Url.hs b/Utility/Url.hs
--- a/Utility/Url.hs
+++ b/Utility/Url.hs
@@ -281,7 +281,7 @@
 					fn <- extractFromResourceT (extractfilename resp)
 					return $ found len fn
 				else if responseStatus resp == unauthorized401
-					then return $ getBasicAuth uo' (show (getUri req)) >>= \case
+					then return $ getBasicAuth uo' (show (getUri req)) (responseHeaders resp) >>= \case
 						Nothing -> return dne
 						Just (ba, signalsuccess) -> do
 							ui <- existsconduit'
@@ -476,7 +476,7 @@
 				else do
 					rf <- extractFromResourceT (respfailure resp)
 					if responseStatus resp == unauthorized401
-						then return $ getBasicAuth uo (show (getUri req')) >>= \case
+						then return $ getBasicAuth uo (show (getUri req')) (responseHeaders resp) >>= \case
 							Nothing -> giveup rf
 							Just ba -> retryauthed ba
 						else return $ giveup rf
@@ -516,7 +516,7 @@
 					else do
 						rf <- extractFromResourceT (respfailure resp)
 						if responseStatus resp == unauthorized401
-							then return $ getBasicAuth uo (show (getUri req'')) >>= \case
+							then return $ getBasicAuth uo (show (getUri req'')) (responseHeaders resp) >>= \case
 								Nothing -> giveup rf
 								Just ba -> retryauthed ba
 							else return $ giveup rf
@@ -725,10 +725,10 @@
 --
 -- The returned IO action is run after trying to use the BasicAuth,
 -- indicating if the password worked.
-type GetBasicAuth = URLString -> IO (Maybe (BasicAuth, Bool -> IO ()))
+type GetBasicAuth = URLString -> ResponseHeaders -> IO (Maybe (BasicAuth, Bool -> IO ()))
 
 noBasicAuth :: GetBasicAuth
-noBasicAuth = const $ pure Nothing
+noBasicAuth = const $ const $ pure Nothing
 
 applyBasicAuth' :: BasicAuth -> Request -> Request
 applyBasicAuth' ba = applyBasicAuth
diff --git a/Utility/UserInfo.hs b/Utility/UserInfo.hs
--- a/Utility/UserInfo.hs
+++ b/Utility/UserInfo.hs
@@ -19,9 +19,7 @@
 #ifndef mingw32_HOST_OS
 import Utility.Data
 import System.Posix.User
-#if MIN_VERSION_unix(2,8,0)
 import System.Posix.User.ByteString (UserEntry)
-#endif
 #endif
 
 {- Current user's home directory.
diff --git a/git-annex.cabal b/git-annex.cabal
--- a/git-annex.cabal
+++ b/git-annex.cabal
@@ -1,5 +1,5 @@
 Name: git-annex
-Version: 10.20251215
+Version: 10.20260115
 Cabal-Version: 1.12
 License: AGPL-3
 Maintainer: Joey Hess <id@joeyh.name>
@@ -96,11 +96,8 @@
   templates/configurators/newrepository/first.hamlet
   templates/configurators/newrepository/combine.hamlet
   templates/configurators/enablewebdav.hamlet
-  templates/configurators/pairing/local/inprogress.hamlet
-  templates/configurators/pairing/local/prompt.hamlet
   templates/configurators/pairing/wormhole/prompt.hamlet
   templates/configurators/pairing/wormhole/start.hamlet
-  templates/configurators/pairing/disabled.hamlet
   templates/configurators/addglacier.hamlet
   templates/configurators/fsck.cassius
   templates/configurators/edit/nonannexremote.hamlet
@@ -152,9 +149,6 @@
   Description: Enable git-annex assistant, webapp, and watch command
   Default: True
 
-Flag Pairing
-  Description: Enable pairing
-
 Flag Production
   Description: Enable production build (slower build; faster binary)
 
@@ -186,11 +180,11 @@
 
 source-repository head
   type: git
-  location: git://git-annex.branchable.com/
+  location: https://git.joeyh.name/git/git-annex.git/
 
 custom-setup
   Setup-Depends:
-    base (>= 4.15.1.0 && < 5.0),
+    base (>= 4.18.2.1 && < 5.0),
     split,
     filepath,
     exceptions,
@@ -206,12 +200,12 @@
 Executable git-annex
   Main-Is: git-annex.hs
   Build-Depends:
-   base (>= 4.15.1.0 && < 5.0),
+   base (>= 4.18.2.1 && < 5.0),
    network-uri (>= 2.6),
    optparse-applicative (>= 0.14.2),
    containers (>= 0.5.8),
    exceptions (>= 0.6),
-   stm (>= 2.3),
+   stm (>= 2.5.1.0),
    mtl (>= 2),
    uuid (>= 1.2.6),
    process (>= 1.6.4),
@@ -229,8 +223,8 @@
    monad-logger (>= 0.3.10),
    free,
    utf8-string (>= 1.0.0),
-   bytestring,
-   text,
+   bytestring (>= 0.11.5.3),
+   text (>= 2.0.2),
    sandi,
    monad-control,
    transformers,
@@ -245,8 +239,8 @@
    conduit,
    time (>= 1.9.1),
    old-locale,
-   persistent-sqlite (>= 2.8.1),
-   persistent (>= 2.8.1),
+   persistent-sqlite (>= 2.13.3),
+   persistent (>= 2.13.3),
    persistent-template (>= 2.8.0),
    unliftio-core,
    microlens,
@@ -325,12 +319,12 @@
 
   if (os(windows))
     Build-Depends:
-      Win32 ((>= 2.6.1.0 && < 2.12.0.0) || >= 2.13.4.0),
+      Win32 (>= 2.13.4.0),
       setenv,
       process (>= 1.6.2.0),
       silently (>= 1.2.5.1)
   else
-    Build-Depends: unix (>= 2.7.2)
+    Build-Depends: unix (>= 2.8.4)
 
   if flag(Assistant) && ! os(solaris) && ! os(gnu)
     CPP-Options: -DWITH_ASSISTANT -DWITH_WEBAPP
@@ -367,8 +361,6 @@
       Assistant.Monad
       Assistant.NamedThread
       Assistant.Pairing
-      Assistant.Pairing.MakeRemote
-      Assistant.Pairing.Network
       Assistant.Pushes
       Assistant.RemoteControl
       Assistant.Repair
@@ -386,7 +378,6 @@
       Assistant.Threads.Merger
       Assistant.Threads.MountWatcher
       Assistant.Threads.NetWatcher
-      Assistant.Threads.PairListener
       Assistant.Threads.ProblemFixer
       Assistant.Threads.Pusher
       Assistant.Threads.RemoteControl
@@ -484,10 +475,6 @@
       CPP-Options: -DWITH_DBUS -DWITH_DESKTOP_NOTIFY -DWITH_DBUS_NOTIFICATIONS
       Other-Modules: Utility.DBus
 
-  if flag(Pairing)
-    Build-Depends: network-multicast, network-info
-    CPP-Options: -DWITH_PAIRING
-
   if flag(TorrentParser)
     Build-Depends: torrent (>= 10000.0.0)
     CPP-Options: -DWITH_TORRENTPARSER
@@ -789,7 +776,6 @@
     Database.RepoSize
     Database.RepoSize.Handle
     Database.Types
-    Database.Utility
     Git
     Git.AutoCorrect
     Git.Branch
@@ -1126,7 +1112,6 @@
     Utility.SshConfig
     Utility.SshHost
     Utility.StatelessOpenPGP
-    Utility.STM
     Utility.Su
     Utility.SystemDirectory
     Utility.FileIO
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -3,7 +3,6 @@
     production: true
     parallelbuild: true
     assistant: true
-    pairing: true
     torrentparser: true
     magicmime: false
     dbus: false
@@ -12,6 +11,6 @@
     ospath: true
 packages:
 - '.'
-resolver: lts-24.2
+resolver: lts-24.26
 extra-deps:
 - aws-0.25.2
diff --git a/templates/configurators/addrepository/misc.hamlet b/templates/configurators/addrepository/misc.hamlet
--- a/templates/configurators/addrepository/misc.hamlet
+++ b/templates/configurators/addrepository/misc.hamlet
@@ -11,14 +11,6 @@
 ^{makeWormholePairing}
 
 <h3>
-  <a href="@{StartLocalPairR}">
-    <span .glyphicon .glyphicon-plus-sign>
-    \ Local computer
-<p>
-  Pair with a computer to keep files in sync quickly, #
-  over your local network.
-
-<h3>
   <a href="@{NewRepositoryR}">
     <span .glyphicon .glyphicon-plus-sign>
     \ Add another repository
diff --git a/templates/configurators/pairing/disabled.hamlet b/templates/configurators/pairing/disabled.hamlet
deleted file mode 100644
--- a/templates/configurators/pairing/disabled.hamlet
+++ /dev/null
@@ -1,6 +0,0 @@
-<div .col-sm-9>
-  <div .content-box>
-    <h2>
-      not supported
-    <p>
-      This build of git-annex does not support #{pairingtype} pairing. Sorry!
diff --git a/templates/configurators/pairing/local/inprogress.hamlet b/templates/configurators/pairing/local/inprogress.hamlet
deleted file mode 100644
--- a/templates/configurators/pairing/local/inprogress.hamlet
+++ /dev/null
@@ -1,19 +0,0 @@
-<div .col-sm-9>
-  <div .content-box>
-    <h2>
-      Pairing in progress ..
-    $if T.null secret
-      <p>
-        You do not need to leave this page open; pairing will finish #
-        automatically.
-    $else
-      <p>
-        Now you should either go tell the owner of the computer you want to pair #
-        with the secret phrase you selected ("#{secret}"), or go enter it into #
-        the computer you want to pair with.
-      <p>
-        You do not need to leave this page open; pairing will finish automatically #
-        as soon as the secret phrase is entered into the other computer.
-      <p>
-        If you're not seeing a pair request on the other computer, try moving #
-        it to the same switch or wireless network as this one.
diff --git a/templates/configurators/pairing/local/prompt.hamlet b/templates/configurators/pairing/local/prompt.hamlet
deleted file mode 100644
--- a/templates/configurators/pairing/local/prompt.hamlet
+++ /dev/null
@@ -1,53 +0,0 @@
-<div .col-sm-9>
-  <div .content-box>
-    <h2>
-      Pairing with a local computer
-    <p>
-      $if start
-        Pair with a computer on your local network (or VPN), and the #
-        two git annex repositories will be combined into one, with changes #
-        kept in sync between them.
-      $else
-        Pairing with #{username}@#{hostname} will combine your two git annex #
-        repositories into one, allowing you to share files.
-    <p>
-      $if start
-        For security, enter a secret phrase. To verify that you're pairing #
-        with the right computer, its git-annex webapp will then prompt for the #
-        same secret phrase. That's all this secret phrase will be used for.
-      $else
-        $if sameusername
-          For security, you need to enter the same secret phrase that was #
-          entered on #{hostname} when the pairing was started.
-        $else
-          For security, a secret phrase has been selected, which you need #
-          to enter here to finish the pairing. If you don't know the #
-          phrase, go ask #{username} ...
-    $if badphrase
-      <div .alert .alert-danger>
-        <span .glyphicon .glyphicon-warning-sign>
-        \ #{problem}
-    <p>
-      <form method="post" .form-horizontal enctype=#{enctype}>
-        <fieldset>
-          ^{form}
-          ^{webAppFormAuthToken}
-          <div .form-group>
-            <div .col-sm-10 .col-sm-offset-2>
-              <button .btn .btn-primary type=submit>
-                $if start
-                  Start pairing
-                $else
-                  Finish pairing
-      <div .alert .alert-info>
-        $if start
-          <p>
-            A good secret phrase is reasonably long. You'll only #
-            type it a few times. Only letters and numbers matter; #
-            punctuation and white space is ignored.
-          <p>
-            A quotation is one good choice, something like: #
-            "#{sampleQuote}"
-        $else
-          Only letters and numbers matter; punctuation and spaces are #
-          ignored.
