diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Changelog
 
+## [0.1.1.0] - 2026-07-29
+
+### Fixed
+
+* `writeManifestTxtFile` now writes UTF-8.  It wrote through a binary handle
+  with `hPutStr`, which truncates every `Char` to its low byte, so any
+  non-ASCII character in a rendered source line came out as a different
+  character (`…` as `&`) or as an invalid UTF-8 byte.  The `.json` manifest
+  was unaffected, as is the driver's `report.txt`, which already encoded
+  explicitly.
+
 ## [0.1.0.0] - 2026-07-16
 
 * First released version.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Test/Syd/Mutation/Manifest/Render.hs b/src/Test/Syd/Mutation/Manifest/Render.hs
--- a/src/Test/Syd/Mutation/Manifest/Render.hs
+++ b/src/Test/Syd/Mutation/Manifest/Render.hs
@@ -22,14 +22,15 @@
   )
 where
 
+import qualified Data.ByteString as SB
 import Data.Maybe (mapMaybe)
 import Data.Text (Text)
 import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
 import qualified Data.Vector as V
 import Myers.Diff (PolyDiff (..), getGroupedDiff, getTextDiff)
 import Path
 import Path.IO (ensureDir)
-import qualified System.IO as IO
 import Test.Syd.Mutation.Manifest (MutationGroup (..), MutationManifest (..), MutationRecord (..))
 import Test.Syd.Mutation.Runtime (MutationId (..))
 import Text.Colour
@@ -131,6 +132,11 @@
 -- 8-bit ANSI escapes embedded.  Reviewers can read with @cat foo.txt@ or
 -- @less -R foo.txt@.
 --
+-- Writes UTF-8 bytes directly, like the driver's @report.txt@: the rendering
+-- embeds source lines verbatim, so it carries whatever characters the mutated
+-- module contains, and the plugin runs in build sandboxes where the locale's
+-- encoding is not UTF-8.
+--
 -- An empty manifest still produces a file (containing just the module
 -- header) so the directory keeps a 1:1 correspondence between @.json@ and
 -- @.txt@ entries.
@@ -141,9 +147,7 @@
   let rendered = renderManifest moduleName manifest
       txt = renderChunksText With8BitColours (unlinesChunks rendered)
       path = fromAbsFile (dir </> fileName)
-  IO.withFile path IO.WriteMode $ \h -> do
-    IO.hSetBinaryMode h True
-    IO.hPutStr h (T.unpack txt)
+  SB.writeFile path (TE.encodeUtf8 txt)
 
 -- ---------------------------------------------------------------------------
 -- Diff rendering (shared with sydtest's MutationMode.Common)
diff --git a/sydtest-mutation-runtime.cabal b/sydtest-mutation-runtime.cabal
--- a/sydtest-mutation-runtime.cabal
+++ b/sydtest-mutation-runtime.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           sydtest-mutation-runtime
-version:        0.1.0.0
+version:        0.1.1.0
 synopsis:       Runtime support library for sydtest's mutation testing
 description:    Runtime support library for sydtest's mutation testing. It provides the manifest, coverage and identifier types shared between the core sydtest library, the mutation plugin, and the mutation driver. See https://github.com/NorfairKing/sydtest#readme for more information.
 category:       Testing
