diff --git a/CHANGES.md b/CHANGES.md
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -17,6 +17,9 @@
 For user-visible changes, see the hledger package changelog.
 
 
+# 1.51.1 2025-12-08
+
+
 # 1.51 2025-12-05
 
 Breaking changes
diff --git a/Hledger/Read.hs b/Hledger/Read.hs
--- a/Hledger/Read.hs
+++ b/Hledger/Read.hs
@@ -167,6 +167,7 @@
 import Prelude hiding (getContents, writeFile)
 import Hledger.Data.JournalChecks (journalStrictChecks)
 import Text.Printf (printf)
+import Hledger.Data.Journal (journalNumberTransactions)
 
 --- ** doctest setup
 -- $setup
@@ -223,7 +224,7 @@
     exists <- doesFileExist defaultfile
     if exists then return defaultfile
     -- else error' $ "LEDGER_FILE is unset and \"" <> defaultfile <> "\" was not found"
-    else error' $ "neither LEDGER_FILE nor \"" <> defaultfile <> "\" were found"
+    else error' $ "neither LEDGER_FILE nor \"" <> defaultfile <> "\" was found"
   else do
     mf <- headMay <$> expandGlob "." ledgerfile `C.catch` (\(_::C.IOException) -> return [])
     case mf of
@@ -350,10 +351,12 @@
 -- The implementation of readJournalFiles.
 -- With --new, it also returns the latest transaction date(s) read in each file
 -- (used by the import command).
+-- This also renumbers the transactions, ensuring their tindex values are unique;
+-- that's also done elsewhere, but some code (accountTransactionsReport) needs it done sooner.
 readJournalFilesAndLatestDates :: InputOpts -> [PrefixedFilePath] -> ExceptT String IO (Journal, [LatestDatesForFile])
 readJournalFilesAndLatestDates iopts pfs = do
   (js, lastdates) <- unzip <$> mapM (readJournalFileAndLatestDates iopts) pfs
-  return (maybe def sconcat $ nonEmpty js, catMaybes lastdates)
+  return (journalNumberTransactions $ maybe def sconcat $ nonEmpty js, catMaybes lastdates)
 
 -- | An easy version of 'readJournal' which assumes default options, and fails in the IO monad.
 readJournal' :: Handle -> IO Journal
diff --git a/Hledger/Read/JournalReader.hs b/Hledger/Read/JournalReader.hs
--- a/Hledger/Read/JournalReader.hs
+++ b/Hledger/Read/JournalReader.hs
@@ -106,6 +106,7 @@
 import Hledger.Read.RulesReader qualified as RulesReader (reader)
 import Hledger.Read.TimeclockReader qualified as TimeclockReader (reader)
 import Hledger.Read.TimedotReader qualified as TimedotReader (reader)
+import Data.Function ((&))
 
 --- ** doctest setup
 -- $setup
@@ -318,7 +319,7 @@
     ) <|> errorNoArg
 
   let (mprefix,glb) = splitReaderPrefix prefixedglob
-  parentf <- sourcePosFilePath pos   -- a little slow, don't do too often
+  parentf <- sourcePosFilePath pos
   when (null $ dbg6 (parentf <> " include: glob pattern") glb) errorNoArg
 
   -- Find the file or glob-matched files (just the ones from this include directive), with some IO error checking.
@@ -342,7 +343,7 @@
         Just fmt -> map ((show fmt++":")++) paths
 
   -- Parse each one, as if inlined here.
-  forM_ prefixedpaths $ parseIncludedFile iopts
+  forM_ prefixedpaths $ parseIncludedFile iopts eoff
 
   where
 
@@ -375,10 +376,11 @@
       -- but it does search non-top-level dot directories. Eg ** will find the c file in a/.b/c.
 
       -- expand a tilde at the start of the glob pattern, or throw an error
-      expandedglob <- lift $ expandHomePath globpattern `orRethrowIOError` "failed to expand ~"
+      expandedglob <- lift $ expandHomePath globpattern & handleIOError off "failed to expand ~"
 
       -- get the directory of the including file
-      let cwd = takeDirectory parentf
+      -- need to canonicalise a symlink parentf so takeDirectory works correctly [#2503]
+      cwd <- fmap takeDirectory <$> liftIO $ canonicalizePath parentf
 
       -- Don't allow 3 or more stars.
       when ("***" `isInfixOf` expandedglob) $
@@ -434,12 +436,12 @@
 
     -- Parse the given included file (and any deeper includes, recursively) as if it was inlined in the current (parent) file.
     -- The offset of the start of the include directive in the parent file is provided for error messages.
-    parseIncludedFile :: MonadIO m => InputOpts -> PrefixedFilePath -> ErroringJournalParser m ()
-    parseIncludedFile iopts1 prefixedpath = do
+    parseIncludedFile :: MonadIO m => InputOpts -> Int -> PrefixedFilePath -> ErroringJournalParser m ()
+    parseIncludedFile iopts1 off prefixedpath = do
       let (_mprefix,filepath) = splitReaderPrefix prefixedpath
 
       -- Read the file's content, or throw an error
-      childInput <- lift $ readFilePortably filepath `orRethrowIOError` "failed to read a file"
+      childInput <- lift $ readFilePortably filepath & handleIOError off "failed to read a file"
       parentj <- get
       let initChildj = newJournalWithParseStateFrom filepath parentj
 
@@ -493,14 +495,14 @@
 sourcePosFilePath :: (MonadIO m) => SourcePos -> m FilePath
 sourcePosFilePath = liftIO . makeAbsolute . sourceName
 
--- | Lift an IO action into the exception monad, rethrowing any IO
--- error with the given message prepended.
-orRethrowIOError :: MonadIO m => IO a -> String -> TextParser m a
-orRethrowIOError io msg = do
+-- | Lift an IO action into the exception monad, converting any IO error
+-- to a parse error message at the given offset.
+handleIOError :: MonadIO m => Int -> String -> IO a -> TextParser m a
+handleIOError off msg io = do
   eResult <- liftIO $ (Right <$> io) `C.catch` \(e::C.IOException) -> pure $ Left $ printf "%s:\n%s" msg (show e)
   case eResult of
     Right res -> pure res
-    Left errMsg -> fail errMsg
+    Left errMsg -> setOffset off >> fail errMsg
 
 -- Parse an account directive, adding its info to the journal's
 -- list of account declarations.
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.51
+version:        1.51.1
 synopsis:       A library providing the core functionality of hledger
 description:    This library contains hledger's core functionality.
                 It is used by most hledger* packages so that they support the same
@@ -124,7 +124,7 @@
   hs-source-dirs:
       ./
   ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind
-  cpp-options: -DVERSION="1.51"
+  cpp-options: -DVERSION="1.51.1"
   build-depends:
       Decimal >=0.5.1
     , Glob >=0.9
@@ -184,7 +184,7 @@
   hs-source-dirs:
       test
   ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind
-  cpp-options: -DVERSION="1.51"
+  cpp-options: -DVERSION="1.51.1"
   build-depends:
       Decimal >=0.5.1
     , Glob >=0.7
@@ -245,7 +245,7 @@
   hs-source-dirs:
       test
   ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind
-  cpp-options: -DVERSION="1.51"
+  cpp-options: -DVERSION="1.51.1"
   build-depends:
       Decimal >=0.5.1
     , Glob >=0.9
