diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -9,6 +9,17 @@
 Internal/api/developer-ish changes in the hledger-lib (and hledger) packages.
 For user-visible changes, see the hledger package changelog.
 
+# 1.29.1 2023-03-16
+
+- Hledger.Utils.String:
+
+       added:
+       strip1Char
+       stripBy
+       strip1By
+
+- Allow building with GHC 9.6.1; add base-compat (#2011)
+
 # 1.29 2023-03-11
 
 - added terminal colour detection helpers:
diff --git a/Hledger/Data/Amount.hs b/Hledger/Data/Amount.hs
--- a/Hledger/Data/Amount.hs
+++ b/Hledger/Data/Amount.hs
@@ -144,7 +144,8 @@
   tests_Amount
 ) where
 
-import Control.Applicative (liftA2)
+import Prelude hiding (Applicative(..))
+import Control.Applicative (Applicative(..))
 import Control.Monad (foldM)
 import Data.Char (isDigit)
 import Data.Decimal (DecimalRaw(..), decimalPlaces, normalizeDecimal, roundTo)
diff --git a/Hledger/Data/Balancing.hs b/Hledger/Data/Balancing.hs
--- a/Hledger/Data/Balancing.hs
+++ b/Hledger/Data/Balancing.hs
@@ -26,13 +26,16 @@
 )
 where
 
+import Control.Monad (forM, forM_, when, unless)
 import Control.Monad.Except (ExceptT(..), runExceptT, throwError)
 import "extra" Control.Monad.Extra (whenM)
-import Control.Monad.Reader as R
+import Control.Monad.Reader as R (ReaderT, reader, runReaderT, ask, asks)
 import Control.Monad.ST (ST, runST)
+import Control.Monad.Trans.Class (lift)
 import Data.Array.ST (STArray, getElems, newListArray, writeArray)
 import Data.Foldable (asum)
 import Data.Function ((&))
+import "base-compat" Data.Functor.Compat (void)
 import qualified Data.HashTable.Class as H (toList)
 import qualified Data.HashTable.ST.Cuckoo as H
 import Data.List (partition, sortOn)
diff --git a/Hledger/Data/Dates.hs b/Hledger/Data/Dates.hs
--- a/Hledger/Data/Dates.hs
+++ b/Hledger/Data/Dates.hs
@@ -82,10 +82,11 @@
 , intervalBoundaryBefore)
 where
 
-import qualified Control.Monad.Fail as Fail (MonadFail, fail)
-import Control.Applicative (liftA2)
+import Prelude hiding (Applicative(..))
+import Control.Applicative (Applicative(..))
 import Control.Applicative.Permutations
 import Control.Monad (guard, unless)
+import qualified Control.Monad.Fail as Fail (MonadFail, fail)
 import Data.Char (digitToInt, isDigit, ord)
 import Data.Default (def)
 import Data.Foldable (asum)
diff --git a/Hledger/Data/TransactionModifier.hs b/Hledger/Data/TransactionModifier.hs
--- a/Hledger/Data/TransactionModifier.hs
+++ b/Hledger/Data/TransactionModifier.hs
@@ -12,7 +12,8 @@
 )
 where
 
-import Control.Applicative ((<|>), liftA2)
+import Prelude hiding (Applicative(..))
+import Control.Applicative (Applicative(..), (<|>))
 import qualified Data.Map as M
 import Data.Maybe (catMaybes)
 import qualified Data.Text as T
diff --git a/Hledger/Read.hs b/Hledger/Read.hs
--- a/Hledger/Read.hs
+++ b/Hledger/Read.hs
@@ -53,7 +53,8 @@
 --- ** imports
 import qualified Control.Exception as C
 import Control.Monad (unless, when)
-import "mtl" Control.Monad.Except (ExceptT(..), runExceptT, liftIO, MonadIO)
+import "mtl" Control.Monad.Except (ExceptT(..), runExceptT)
+import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.Default (def)
 import Data.Foldable (asum)
 import Data.List (group, sort, sortBy)
diff --git a/Hledger/Read/Common.hs b/Hledger/Read/Common.hs
--- a/Hledger/Read/Common.hs
+++ b/Hledger/Read/Common.hs
@@ -119,15 +119,18 @@
 
 --- ** imports
 import Control.Applicative.Permutations (runPermutation, toPermutationWithDefault)
+import Control.Monad (foldM, liftM2, when, unless, (>=>), (<=<))
 import qualified Control.Monad.Fail as Fail (fail)
 import Control.Monad.Except (ExceptT(..), liftEither, withExceptT)
-import Control.Monad.State.Strict hiding (fail)
+import Control.Monad.IO.Class (MonadIO, liftIO)
+import Control.Monad.State.Strict (MonadState, evalStateT, modify', get, put)
+import Control.Monad.Trans.Class (lift)
 import Data.Bifunctor (bimap, second)
 import Data.Char (digitToInt, isDigit, isSpace)
 import Data.Decimal (DecimalRaw (Decimal), Decimal)
 import Data.Either (lefts, rights)
 import Data.Function ((&))
-import Data.Functor ((<&>), ($>))
+import Data.Functor ((<&>), ($>), void)
 import Data.List (find, genericReplicate, union)
 import Data.List.NonEmpty (NonEmpty(..))
 import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe)
diff --git a/Hledger/Read/CsvReader.hs b/Hledger/Read/CsvReader.hs
--- a/Hledger/Read/CsvReader.hs
+++ b/Hledger/Read/CsvReader.hs
@@ -35,7 +35,8 @@
 where
 
 --- ** imports
-import Control.Applicative        (liftA2)
+import Prelude hiding (Applicative(..))
+import Control.Applicative (Applicative(..))
 import Control.Monad              (unless, when, void)
 import Control.Monad.Except       (ExceptT(..), liftEither, throwError)
 import qualified Control.Monad.Fail as Fail
diff --git a/Hledger/Reports/ReportOptions.hs b/Hledger/Reports/ReportOptions.hs
--- a/Hledger/Reports/ReportOptions.hs
+++ b/Hledger/Reports/ReportOptions.hs
@@ -64,7 +64,8 @@
 )
 where
 
-import Control.Applicative (Const(..), (<|>), liftA2)
+import Prelude hiding (Applicative(..))
+import Control.Applicative (Applicative(..), Const(..), (<|>))
 import Control.Monad ((<=<), guard, join)
 import Data.Char (toLower)
 import Data.Either (fromRight)
diff --git a/Hledger/Utils/String.hs b/Hledger/Utils/String.hs
--- a/Hledger/Utils/String.hs
+++ b/Hledger/Utils/String.hs
@@ -20,6 +20,9 @@
  strip,
  lstrip,
  rstrip,
+ strip1Char,
+ stripBy,
+ strip1By,
  chomp,
  chomp1,
  singleline,
@@ -35,7 +38,7 @@
 
 
 import Data.Char (isSpace, toLower, toUpper)
-import Data.List (intercalate)
+import Data.List (intercalate, dropWhileEnd)
 import qualified Data.Text as T
 import Text.Megaparsec ((<|>), between, many, noneOf, sepBy)
 import Text.Megaparsec.Char (char)
@@ -68,6 +71,25 @@
 -- | Remove trailing whitespace.
 rstrip :: String -> String
 rstrip = reverse . lstrip . reverse
+
+-- | Strip the given starting and ending character
+-- from the start and end of a string if both are present.
+strip1Char :: Char -> Char -> String -> String
+strip1Char b e s = case s of
+  (c:cs) | c==b, not $ null cs, last cs==e -> init cs
+  _ -> s
+
+-- | Strip a run of zero or more characters matching the predicate
+-- from the start and end of a string.
+stripBy :: (Char -> Bool) -> String -> String
+stripBy f = dropWhileEnd f . dropWhile f
+
+-- | Strip a single balanced enclosing pair of a character matching the predicate
+-- from the start and end of a string.
+strip1By :: (Char -> Bool) -> String -> String
+strip1By f s = case s of
+  (c:cs) | f c, not $ null cs, last cs==c -> init cs
+  _ -> s
 
 -- | Remove all trailing newlines/carriage returns.
 chomp :: String -> String
diff --git a/Hledger/Utils/Test.hs b/Hledger/Utils/Test.hs
--- a/Hledger/Utils/Test.hs
+++ b/Hledger/Utils/Test.hs
@@ -20,7 +20,8 @@
 )
 where
 
-import Control.Monad.Except (ExceptT(..), liftEither, runExceptT, withExceptT, unless)
+import Control.Monad (unless)
+import Control.Monad.Except (ExceptT(..), liftEither, runExceptT, withExceptT)
 import Control.Monad.State.Strict (StateT, evalStateT, execStateT)
 import Data.Default (Default(..))
 import Data.List (isInfixOf)
diff --git a/Text/Megaparsec/Custom.hs b/Text/Megaparsec/Custom.hs
--- a/Text/Megaparsec/Custom.hs
+++ b/Text/Megaparsec/Custom.hs
@@ -50,8 +50,9 @@
 )
 where
 
-import Control.Monad.Except
+import Control.Monad.Except (ExceptT, MonadError, catchError, throwError)
 import Control.Monad.State.Strict (StateT, evalStateT)
+import Control.Monad.Trans.Class (lift)
 import qualified Data.List.NonEmpty as NE
 import Data.Monoid (Alt(..))
 import qualified Data.Set as S
diff --git a/hledger-lib.cabal b/hledger-lib.cabal
--- a/hledger-lib.cabal
+++ b/hledger-lib.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           hledger-lib
-version:        1.29
+version:        1.29.1
 synopsis:       A reusable library providing the core functionality of hledger
 description:    A reusable library containing hledger's core functionality.
                 This is used by most hledger* packages so that they support the same
@@ -27,7 +27,7 @@
 license-file:   LICENSE
 build-type:     Simple
 tested-with:
-    GHC==8.10.7, GHC==9.0.2, GHC==9.2.4
+    GHC==8.10.7, GHC==9.0.2, GHC==9.2.7, GHC==9.4.4
 extra-source-files:
     CHANGES.md
     README.md
@@ -104,7 +104,8 @@
     , aeson-pretty
     , ansi-terminal >=0.9
     , array
-    , base >=4.14 && <4.18
+    , base >=4.14 && <4.19
+    , base-compat
     , blaze-markup >=0.5.1
     , bytestring
     , call-stack
@@ -160,7 +161,8 @@
     , aeson-pretty
     , ansi-terminal >=0.9
     , array
-    , base >=4.14 && <4.18
+    , base >=4.14 && <4.19
+    , base-compat
     , blaze-markup >=0.5.1
     , bytestring
     , call-stack
@@ -219,7 +221,8 @@
     , aeson-pretty
     , ansi-terminal >=0.9
     , array
-    , base >=4.14 && <4.18
+    , base >=4.14 && <4.19
+    , base-compat
     , blaze-markup >=0.5.1
     , bytestring
     , call-stack
