diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
 # Revision history for scripths
 
 
+## 0.5.5.0 -- 2026-08-15
+* An untagged code fence (bare ```` ``` ````) is no longer a code block: it
+  stays prose, verbatim. Only fences with an explicit language tag parse as
+  `CodeBlock`; there is no language to execute an untagged fence under.
+
 ## 0.5.4.2 -- 2026-08-13
 * fixes heuristic to parse binds.
 * declarations separated by a single blank line and/or comment lines merge into one `:{ … :}` block, so mutually recursive definitions with commented section headers load together. A double blank line still splits blocks.
diff --git a/scripths.cabal b/scripths.cabal
--- a/scripths.cabal
+++ b/scripths.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               scripths
-version:            0.5.4.2
+version:            0.5.5.0
 synopsis:           GHCi scripts for standalone execution and Markdown documentation.
 description:        GHCi scripts for standalone execution (with dependency resolution) and Markdown documentation (produces inline output).
 homepage:           https://www.datahaskell.org/
diff --git a/src/ScriptHs/Markdown.hs b/src/ScriptHs/Markdown.hs
--- a/src/ScriptHs/Markdown.hs
+++ b/src/ScriptHs/Markdown.hs
@@ -112,13 +112,15 @@
 fence = "```"
 
 {- | The language tag of a code-fence /opener/, or 'Nothing' if the line is not
-one. A fence is /exactly/ three backticks (optionally followed by a language
-tag) — a line of four or more backticks is not a fence and stays prose.
+one. Only /exactly/ three backticks followed by a language tag open a block:
+an untagged fence has no language to execute under and stays prose, and four
+or more backticks are not a fence at all.
 -}
 fenceLang :: Text -> Maybe Text
 fenceLang line = do
     rest <- T.stripPrefix fence line
-    if "`" `T.isPrefixOf` rest then Nothing else Just (T.strip rest)
+    let tag = T.strip rest
+    if "`" `T.isPrefixOf` rest || T.null tag then Nothing else Just tag
 
 {- | The HTML-comment marker that tags a code block's rendered output with its
 MIME type. We render the @scripths:mime@ form; for backward compatibility we
diff --git a/test/Test/Markdown.hs b/test/Test/Markdown.hs
--- a/test/Test/Markdown.hs
+++ b/test/Test/Markdown.hs
@@ -76,6 +76,46 @@
                 case segs of
                     [CodeBlock "haskell" _ _] -> pure ()
                     other -> assertFailure $ "expected [CodeBlock haskell], got: " ++ show other
+            , testCase "an untagged fence stays prose" $ do
+                let input =
+                        T.unlines
+                            [ "Some text."
+                            , ""
+                            , "```"
+                            , "plain text, not code"
+                            , "```"
+                            ]
+                case parseMarkdown input of
+                    [Prose t] ->
+                        assertBool
+                            "fence kept verbatim"
+                            (T.isInfixOf "```\nplain text, not code\n```" t)
+                    other -> assertFailure $ "expected [Prose], got: " ++ show other
+            , testCase "an untagged fence round-trips verbatim" $ do
+                let input =
+                        T.unlines
+                            [ "Some text."
+                            , ""
+                            , "```"
+                            , "plain text"
+                            , "```"
+                            ]
+                reassemble (parseMarkdown input) @?= input
+            , testCase "a tagged fence after an untagged one still parses" $ do
+                let input =
+                        T.unlines
+                            [ "```"
+                            , "plain"
+                            , "```"
+                            , ""
+                            , "```haskell"
+                            , "print 42"
+                            , "```"
+                            ]
+                case parseMarkdown input of
+                    [Prose _, CodeBlock "haskell" code _] ->
+                        assertBool "has print 42" (T.isInfixOf "print 42" code)
+                    other -> assertFailure $ "expected [Prose, CodeBlock haskell], got: " ++ show other
             , testCase "prose then code" $ do
                 let input =
                         T.unlines
