diff --git a/System/FilePath/Internal.hs b/System/FilePath/Internal.hs
--- a/System/FilePath/Internal.hs
+++ b/System/FilePath/Internal.hs
@@ -1,6 +1,5 @@
 {-# LANGUAGE PatternGuards #-}
 {-# LANGUAGE TypeApplications #-}
-{-# LANGUAGE MultiWayIf #-}
 
 -- This template expects CPP definitions for:
 --     MODULE_NAME = Posix | Windows
@@ -130,10 +129,9 @@
 #define FILEPATH FilePath
 #else
 import Prelude (fromIntegral)
-import Control.Exception ( SomeException, evaluate, displayException )
+import Control.Exception ( SomeException, evaluate, try, displayException )
 import Control.DeepSeq (force)
 import GHC.IO (unsafePerformIO)
-import System.OsPath.Encoding.Internal (trySafe)
 import qualified Data.Char as C
 #ifdef WINDOWS
 import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
@@ -604,7 +602,6 @@
 -- > Posix:   splitFileName "/" == ("/","")
 -- > Windows: splitFileName "c:" == ("c:","")
 -- > Windows: splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\","fred")
--- > Windows: splitFileName "\\\\?\\A:" == ("\\\\?\\A:","")
 splitFileName :: FILEPATH -> (STRING, STRING)
 splitFileName x = if null path
     then (dotSlash, file)
@@ -647,43 +644,20 @@
   -- or UNC location "\\?\UNC\foo", where path separator is a part of the drive name.
   -- We can test this by trying dropDrive and falling back to splitDrive.
   | isWindows
-  = case uncons2 dirSlash of
-    Just (s1, s2, bs')
-      | isPathSeparator s1
-      -- If bs' is empty, then s2 as the last character of dirSlash must be a path separator,
-      -- so we are in the middle of shared drive.
-      -- Otherwise, since s1 is a path separator, we might be in the middle of UNC path.
-      , null bs' || maybe False isIncompleteUNC (readDriveUNC dirSlash)
-      -> (fp, mempty)
-      -- This handles inputs like "//?/A:" and "//?/A:foo"
-      | isPathSeparator s1
-      , isPathSeparator s2
-      , Just (s3, s4, bs'') <- uncons2 bs'
-      , s3 == _question
-      , isPathSeparator s4
-      , null bs''
-      , Just (drive, rest) <- readDriveLetter file
-      -> (dirSlash <> drive, rest)
-    _ -> (dirSlash, file)
+  , Just (s1, _s2, bs') <- uncons2 dirSlash
+  , isPathSeparator s1
+  -- If bs' is empty, then s2 as the last character of dirSlash must be a path separator,
+  -- so we are in the middle of shared drive.
+  -- Otherwise, since s1 is a path separator, we might be in the middle of UNC path.
+  , null bs' || maybe False isIncompleteUNC (readDriveUNC dirSlash)
+  = (fp, mempty)
   | otherwise
-    = (dirSlash, file)
+  = (dirSlash, file)
   where
     (dirSlash, file) = breakEnd isPathSeparator fp
-    dropExcessTrailingPathSeparators x
-      | hasTrailingPathSeparator x
-      , let x' = dropWhileEnd isPathSeparator x
-      , otherwise = if | null x' -> singleton (last x)
-                       | otherwise -> addTrailingPathSeparator x'
-      | otherwise = x
 
-    -- an "incomplete" UNC is one without a path (but potentially a drive)
     isIncompleteUNC (pref, suff) = null suff && not (hasPenultimateColon pref)
-
-    -- e.g. @//?/a:/@ or @//?/a://@, but not @//?/a:@
-    hasPenultimateColon pref
-      | hasTrailingPathSeparator pref
-      = maybe False (maybe False ((== _colon) . snd) . unsnoc . fst) . unsnoc . dropExcessTrailingPathSeparators $ pref
-      | otherwise = False
+    hasPenultimateColon = maybe False (maybe False ((== _colon) . snd) . unsnoc . fst) . unsnoc
 
 -- | Set the filename.
 --
@@ -1274,12 +1248,12 @@
 #ifdef WINDOWS
 fromString :: P.String -> STRING
 fromString str = P.either (P.error . P.show) P.id $ unsafePerformIO $ do
-  r <- trySafe @SomeException $ GHC.withCStringLen (mkUTF16le ErrorOnCodingFailure) str $ \cstr -> packCStringLen cstr
+  r <- try @SomeException $ GHC.withCStringLen (mkUTF16le ErrorOnCodingFailure) str $ \cstr -> packCStringLen cstr
   evaluate $ force $ first displayException r
 #else
 fromString :: P.String -> STRING
 fromString str = P.either (P.error . P.show) P.id $ unsafePerformIO $ do
-  r <- trySafe @SomeException $ GHC.withCStringLen (mkUTF8 ErrorOnCodingFailure) str $ \cstr -> packCStringLen cstr
+  r <- try @SomeException $ GHC.withCStringLen (mkUTF8 ErrorOnCodingFailure) str $ \cstr -> packCStringLen cstr
   evaluate $ force $ first displayException r
 #endif
 
diff --git a/System/OsPath/Data/ByteString/Short.hs b/System/OsPath/Data/ByteString/Short.hs
--- a/System/OsPath/Data/ByteString/Short.hs
+++ b/System/OsPath/Data/ByteString/Short.hs
@@ -23,7 +23,9 @@
 --
 -- > import qualified Data.ByteString.Short as B.Short
 --
-module System.OsPath.Data.ByteString.Short (
+module System.OsPath.Data.ByteString.Short {-# DEPRECATED "Use System.OsString.Data.ByteString.Short from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-}
+
+ (
 
     -- * The @ShortByteString@ type
 
diff --git a/System/OsPath/Data/ByteString/Short/Internal.hs b/System/OsPath/Data/ByteString/Short/Internal.hs
--- a/System/OsPath/Data/ByteString/Short/Internal.hs
+++ b/System/OsPath/Data/ByteString/Short/Internal.hs
@@ -17,7 +17,7 @@
 --
 -- Internal low-level utilities mostly for 'System.OsPath.Data.ByteString.Short.Word16',
 -- such as byte-array operations and other stuff not meant to be exported from Word16 module.
-module System.OsPath.Data.ByteString.Short.Internal where
+module System.OsPath.Data.ByteString.Short.Internal {-# DEPRECATED "Use System.OsString.Data.ByteString.Short.Internal from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-} where
 
 import Control.Monad.ST
 import Control.Exception (assert, throwIO)
@@ -164,9 +164,13 @@
       action buf
   where l = length bs
 
--- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a @CStringLen@.
--- As for @useAsCString@ this function makes a copy of the original @ShortByteString@.
+-- | /O(n) construction./ Use a @ShortByteString@ with a function requiring a 'CStringLen'.
+-- As for 'useAsCString' this function makes a copy of the original @ShortByteString@.
 -- It must not be stored or used after the subcomputation finishes.
+--
+-- Beware that this function does not add a terminating @\NUL@ byte at the end of 'CStringLen'.
+-- If you need to construct a pointer to a null-terminated sequence, use 'useAsCString'
+-- (and measure length independently if desired).
 --
 -- @since 0.10.10.0
 useAsCStringLen :: ShortByteString -> (CStringLen -> IO a) -> IO a
diff --git a/System/OsPath/Data/ByteString/Short/Word16.hs b/System/OsPath/Data/ByteString/Short/Word16.hs
--- a/System/OsPath/Data/ByteString/Short/Word16.hs
+++ b/System/OsPath/Data/ByteString/Short/Word16.hs
@@ -29,7 +29,7 @@
 --
 -- All functions will error out if the input string is not a valid UTF16 stream (uneven number of bytes).
 -- So use this module with caution.
-module System.OsPath.Data.ByteString.Short.Word16 (
+module System.OsPath.Data.ByteString.Short.Word16 {-# DEPRECATED "Use System.OsString.Data.ByteString.Short.Word16 from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-} (
     -- * The @ShortByteString@ type and representation
     ShortByteString(..),
 
diff --git a/System/OsPath/Encoding/Internal.hs b/System/OsPath/Encoding/Internal.hs
--- a/System/OsPath/Encoding/Internal.hs
+++ b/System/OsPath/Encoding/Internal.hs
@@ -6,7 +6,7 @@
 {-# OPTIONS_GHC  -funbox-strict-fields #-}
 
 
-module System.OsPath.Encoding.Internal where
+module System.OsPath.Encoding.Internal {-# DEPRECATED "Use System.OsString.Encoding.Internal from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-} where
 
 import qualified System.OsPath.Data.ByteString.Short as BS8
 import qualified System.OsPath.Data.ByteString.Short.Word16 as BS16
@@ -19,7 +19,7 @@
 import GHC.IO.Encoding.Failure
 import GHC.IO.Encoding.Types
 import Data.Bits
-import Control.Exception (SomeException, SomeAsyncException(..), Exception (displayException), evaluate, catch, throwIO, toException, fromException)
+import Control.Exception (SomeException, try, Exception (displayException), evaluate)
 import qualified GHC.Foreign as GHC
 import Data.Either (Either)
 import GHC.IO (unsafePerformIO)
@@ -31,7 +31,7 @@
 import Foreign.C (CStringLen)
 import Data.Char (chr)
 import Foreign
-import Prelude (FilePath, Either(..))
+import Prelude (FilePath)
 import GHC.IO.Encoding (getFileSystemEncoding)
 
 -- -----------------------------------------------------------------------------
@@ -277,13 +277,13 @@
 -- | Decode with the given 'TextEncoding'.
 decodeWithTE :: TextEncoding -> BS8.ShortByteString -> Either EncodingException String
 decodeWithTE enc ba = unsafePerformIO $ do
-  r <- trySafe @SomeException $ BS8.useAsCStringLen ba $ \fp -> GHC.peekCStringLen enc fp
+  r <- try @SomeException $ BS8.useAsCStringLen ba $ \fp -> GHC.peekCStringLen enc fp
   evaluate $ force $ first (flip EncodingError Nothing . displayException) r
 
 -- | Encode with the given 'TextEncoding'.
 encodeWithTE :: TextEncoding -> String -> Either EncodingException BS8.ShortByteString
 encodeWithTE enc str = unsafePerformIO $ do
-  r <- trySafe @SomeException $ GHC.withCStringLen enc str $ \cstr -> BS8.packCStringLen cstr
+  r <- try @SomeException $ GHC.withCStringLen enc str $ \cstr -> BS8.packCStringLen cstr
   evaluate $ force $ first (flip EncodingError Nothing . displayException) r
 
 -- -----------------------------------------------------------------------------
@@ -347,25 +347,3 @@
 
 wNUL :: Word16
 wNUL = 0x00
-
--- -----------------------------------------------------------------------------
--- Exceptions
---
-
--- | Like 'try', but rethrows async exceptions.
-trySafe :: Exception e => IO a -> IO (Either e a)
-trySafe ioA = catch action eHandler
- where
-  action = do
-    v <- ioA
-    return (Right v)
-  eHandler e
-    | isAsyncException e = throwIO e
-    | otherwise = return (Left e)
-
-isAsyncException :: Exception e => e -> Bool
-isAsyncException e =
-    case fromException (toException e) of
-        Just (SomeAsyncException _) -> True
-        Nothing -> False
-
diff --git a/System/OsString.hs b/System/OsString.hs
--- a/System/OsString.hs
+++ b/System/OsString.hs
@@ -14,7 +14,7 @@
 --
 -- It captures the notion of syscall specific encoding (or the lack thereof) to avoid roundtrip issues
 -- and memory fragmentation by using unpinned byte arrays. Bytes are not touched or interpreted.
-module System.OsString
+module System.OsString {-# DEPRECATED "Use System.OsString from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-}
   (
   -- * String types
     OsString
diff --git a/System/OsString/Common.hs b/System/OsString/Common.hs
--- a/System/OsString/Common.hs
+++ b/System/OsString/Common.hs
@@ -11,7 +11,7 @@
 #define POSIX_DOC
 #endif
 
-module System.OsString.MODULE_NAME
+module System.OsString.MODULE_NAME {-# DEPRECATED "Use System.OsString.MODULE_NAME from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-}
   (
   -- * Types
 #ifdef WINDOWS
@@ -60,7 +60,7 @@
 import Data.ByteString.Internal
     ( ByteString )
 import Control.Exception
-    ( SomeException, displayException )
+    ( SomeException, try, displayException )
 import Control.DeepSeq ( force )
 import Data.Bifunctor ( first )
 import GHC.IO
@@ -73,7 +73,6 @@
 
 
 import GHC.IO.Encoding.Failure ( CodingFailureMode(..) )
-import System.OsPath.Encoding.Internal (trySafe)
 #ifdef WINDOWS
 import System.OsPath.Encoding
 import System.IO
@@ -117,10 +116,10 @@
            -> Either EncodingException PLATFORM_STRING
 encodeWith enc str = unsafePerformIO $ do
 #ifdef WINDOWS
-  r <- trySafe @SomeException $ GHC.withCStringLen enc str $ \cstr -> WindowsString <$> BS8.packCStringLen cstr
+  r <- try @SomeException $ GHC.withCStringLen enc str $ \cstr -> WindowsString <$> BS8.packCStringLen cstr
   evaluate $ force $ first (flip EncodingError Nothing . displayException) r
 #else
-  r <- trySafe @SomeException $ GHC.withCStringLen enc str $ \cstr -> PosixString <$> BS.packCStringLen cstr
+  r <- try @SomeException $ GHC.withCStringLen enc str $ \cstr -> PosixString <$> BS.packCStringLen cstr
   evaluate $ force $ first (flip EncodingError Nothing . displayException) r
 #endif
 
@@ -177,7 +176,7 @@
            -> PLATFORM_STRING
            -> Either EncodingException String
 decodeWith winEnc (WindowsString ba) = unsafePerformIO $ do
-  r <- trySafe @SomeException $ BS8.useAsCStringLen ba $ \fp -> GHC.peekCStringLen winEnc fp
+  r <- try @SomeException $ BS8.useAsCStringLen ba $ \fp -> GHC.peekCStringLen winEnc fp
   evaluate $ force $ first (flip EncodingError Nothing . displayException) r
 #else
 -- | Decode a 'PosixString' with the specified encoding.
@@ -187,7 +186,7 @@
        -> PLATFORM_STRING
        -> Either EncodingException String
 decodeWith unixEnc (PosixString ba) = unsafePerformIO $ do
-  r <- trySafe @SomeException $ BS.useAsCStringLen ba $ \fp -> GHC.peekCStringLen unixEnc fp
+  r <- try @SomeException $ BS.useAsCStringLen ba $ \fp -> GHC.peekCStringLen unixEnc fp
   evaluate $ force $ first (flip EncodingError Nothing . displayException) r
 #endif
 
diff --git a/System/OsString/Internal.hs b/System/OsString/Internal.hs
--- a/System/OsString/Internal.hs
+++ b/System/OsString/Internal.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE RankNTypes #-}
 {-# LANGUAGE UnliftedFFITypes #-}
 
-module System.OsString.Internal where
+module System.OsString.Internal {-# DEPRECATED "Use System.OsString.Internal from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-} where
 
 import System.OsString.Internal.Types
 
diff --git a/System/OsString/Internal/Types.hs b/System/OsString/Internal/Types.hs
--- a/System/OsString/Internal/Types.hs
+++ b/System/OsString/Internal/Types.hs
@@ -8,7 +8,7 @@
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE PatternSynonyms #-}
 
-module System.OsString.Internal.Types
+module System.OsString.Internal.Types {-# DEPRECATED "Use System.OsString.Internal.Types from os-string >= 2.0.0 package instead. This module will be removed in filepath >= 1.5." #-}
   (
     WindowsString(..)
   , pattern WS
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -2,13 +2,9 @@
 
 _Note: below all `FilePath` values are unquoted, so `\\` really means two backslashes._
 
-## 1.4.102.0 *Nov 2024*
-
-* Don't catch async exceptions in internal functions wrt https://github.com/haskell/os-string/issues/22
-
-## 1.4.101.0 *Jan 2024*
+## 1.4.200.0 *Jul 2023*
 
-* Backport bugfix for [`splitFileName`](https://github.com/haskell/filepath/issues/219) on windows
+* deprecate `OsString` modules
 
 ## 1.4.100.4 *Jul 2023*
 
diff --git a/filepath.cabal b/filepath.cabal
--- a/filepath.cabal
+++ b/filepath.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.2
 name:               filepath
-version:            1.4.102.0
+version:            1.4.200.0
 
 -- NOTE: Don't forget to update ./changelog.md
 license:            BSD-3-Clause
@@ -103,7 +103,7 @@
 
   default-language: Haskell2010
   build-depends:
-    , base              >=4.9      && <4.22
+    , base              >=4.9      && <4.20
     , bytestring        >=0.11.3.0
     , deepseq
     , exceptions
diff --git a/tests/filepath-equivalent-tests/TestEquiv.hs b/tests/filepath-equivalent-tests/TestEquiv.hs
--- a/tests/filepath-equivalent-tests/TestEquiv.hs
+++ b/tests/filepath-equivalent-tests/TestEquiv.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE CPP #-}
-{-# LANGUAGE TypeApplications #-}
 
 module Main where
 
@@ -11,6 +10,7 @@
 import qualified System.FilePath.Posix as P
 import qualified Legacy.System.FilePath.Windows as LW
 import qualified Legacy.System.FilePath.Posix as LP
+import Data.Char (isAsciiLower, isAsciiUpper)
 
 
 main :: IO ()
@@ -189,7 +189,14 @@
     )
     ,
     ( "normalise (windows)"
-    , property $ \p -> W.normalise p == LW.normalise p
+    , property $ \p -> case p of
+                         (l:':':rs)
+                           -- new filepath normalises "a:////////" to "A:\\"
+                           -- see https://github.com/haskell/filepath/commit/cb4890aa03a5ee61f16f7a08dd2d964fffffb385
+                           | isAsciiLower l || isAsciiUpper l
+                           , let (seps, path) = span LW.isPathSeparator rs
+                           , length seps > 1 -> let np = l : ':' : LW.pathSeparator : path in W.normalise np == LW.normalise np
+                         _ -> W.normalise p == LW.normalise p
     )
     ,
     ( "equalFilePath (windows)"
diff --git a/tests/filepath-tests/TestGen.hs b/tests/filepath-tests/TestGen.hs
--- a/tests/filepath-tests/TestGen.hs
+++ b/tests/filepath-tests/TestGen.hs
@@ -458,8 +458,6 @@
     ,("AFP_W.splitFileName (\"c:\") == ((\"c:\"), (\"\"))", property $ AFP_W.splitFileName ("c:") == (("c:"), ("")))
     ,("W.splitFileName \"\\\\\\\\?\\\\A:\\\\fred\" == (\"\\\\\\\\?\\\\A:\\\\\", \"fred\")", property $ W.splitFileName "\\\\?\\A:\\fred" == ("\\\\?\\A:\\", "fred"))
     ,("AFP_W.splitFileName (\"\\\\\\\\?\\\\A:\\\\fred\") == ((\"\\\\\\\\?\\\\A:\\\\\"), (\"fred\"))", property $ AFP_W.splitFileName ("\\\\?\\A:\\fred") == (("\\\\?\\A:\\"), ("fred")))
-    ,("W.splitFileName \"\\\\\\\\?\\\\A:\" == (\"\\\\\\\\?\\\\A:\", \"\")", property $ W.splitFileName "\\\\?\\A:" == ("\\\\?\\A:", ""))
-    ,("AFP_W.splitFileName (\"\\\\\\\\?\\\\A:\") == ((\"\\\\\\\\?\\\\A:\"), (\"\"))", property $ AFP_W.splitFileName ("\\\\?\\A:") == (("\\\\?\\A:"), ("")))
     ,("P.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ P.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")
     ,("W.replaceFileName \"/directory/other.txt\" \"file.ext\" == \"/directory/file.ext\"", property $ W.replaceFileName "/directory/other.txt" "file.ext" == "/directory/file.ext")
     ,("AFP_P.replaceFileName (\"/directory/other.txt\") (\"file.ext\") == (\"/directory/file.ext\")", property $ AFP_P.replaceFileName ("/directory/other.txt") ("file.ext") == ("/directory/file.ext"))
