diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Revision history for stim-parser
 
+## 0.2.0.1 -- 2026-07-18
+
+* Fix noise-channel parsing order in `parseStim`.
+  Noise-channel names such as `X_ERROR`, `Y_ERROR`, `Z_ERROR`, and
+  `I_ERROR` share a prefix with gate names (`X`, `Y`, `Z`, `I`). The
+  top-level dispatcher now tries noise before gates, preventing the gate
+  parser from partially consuming these names and leaving the rest of the
+  input orphaned.
+* Add regression tests for noise-channel parsing through `parseStim`.
+
 ## 0.2.0.0 -- 2026-04-01
 
 * Add DEM (Detector Error Model) parsing support.
diff --git a/src/StimParser/DEM/Expr.hs b/src/StimParser/DEM/Expr.hs
--- a/src/StimParser/DEM/Expr.hs
+++ b/src/StimParser/DEM/Expr.hs
@@ -84,7 +84,9 @@
 flattenInstrs coordShift detShift (i : is) =
   case i of
     DEMInstrShift (DEMShift cs ds) ->
-      let newCoordShift = zipWith (+) (padCoords (length cs) coordShift) cs
+      let newCoordShift = if null cs
+                            then coordShift
+                            else zipWith (+) (padCoords (length cs) coordShift) cs
           newDetShift = detShift + ds
       in flattenInstrs newCoordShift newDetShift is
 
diff --git a/src/StimParser/DEM/Parse.hs b/src/StimParser/DEM/Parse.hs
--- a/src/StimParser/DEM/Parse.hs
+++ b/src/StimParser/DEM/Parse.hs
@@ -9,7 +9,7 @@
 
 -- | Parse a complete DEM string.
 parseDEM :: Parser DEM
-parseDEM = DEM <$> many parseDEMInstruction
+parseDEM = DEM <$> many parseDEMInstruction <* eof
 
 -- | Parse a single DEM instruction.
 parseDEMInstruction :: Parser DEMInstruction
@@ -51,11 +51,13 @@
   oid <- ObservableId <$> (lstring "L" *> parseInt)
   return $ DEMObservable oid
 
--- | Parse a shift_detectors instruction: shift_detectors(1, 0) 0
+-- | Parse a shift_detectors instruction: shift_detectors(1, 0) 0 or shift_detectors 96
+--
+-- Coordinates are optional. When absent, only the detector ID is shifted.
 parseDEMShift :: Parser DEMShift
 parseDEMShift = do
   lstring "shift_detectors"
-  coords <- parseTupleNumber
+  coords <- option [] parseTupleNumber
   shift <- parseInt
   return $ DEMShift coords shift
 
diff --git a/src/StimParser/Parse.hs b/src/StimParser/Parse.hs
--- a/src/StimParser/Parse.hs
+++ b/src/StimParser/Parse.hs
@@ -288,10 +288,14 @@
   let 
     -- the order may be tricky
     parseUnit = 
-      try (StimG <$> parseGate)
+      -- Noise must be tried before gates: noise-channel names such as
+      -- X_ERROR share a prefix with gate names (X, Y, Z, I). If the gate
+      -- parser runs first it can consume just the leading letter and succeed
+      -- with an empty target list, leaving the rest of the input orphaned.
+      try (StimNoise <$> parseNoise)
+      <|> try (StimG <$> parseGate)
       <|> try (StimM <$> parseMeasure)
       <|> try (StimGpp <$> parseGpp)
-      <|> try (StimNoise <$> parseNoise)
       <|> (StimAnn <$> parseAnn)
 
     parseUnitList = StimList <$> parseExhaust parseUnit
diff --git a/stim-parser.cabal b/stim-parser.cabal
--- a/stim-parser.cabal
+++ b/stim-parser.cabal
@@ -20,7 +20,7 @@
 -- PVP summary:     +-+------- breaking API changes
 --                  | | +----- non-breaking API additions
 --                  | | | +--- code changes with no API change
-version:            0.2.0.0
+version:            0.2.0.1
 
 -- A short (one-line) description of the package.
 synopsis:           A parser combinator library for STIM quantum circuit files
diff --git a/test/Test/DEM/Expr.hs b/test/Test/DEM/Expr.hs
--- a/test/Test/DEM/Expr.hs
+++ b/test/Test/DEM/Expr.hs
@@ -16,6 +16,7 @@
   , testFlattenDEMShiftAndRepeat
   , testFlattenDEMDetIdShift
   , testFlattenDEMDimensionMismatch
+  , testFlattenDEMBareShift
   , testFlattenDEMIdempotent
   ]
 
@@ -161,6 +162,45 @@
             ]
           expected = DEM
             [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.0])
+            ]
+      in expected ~=? flattenDEM input
+  ]
+
+-- | shift_detectors without coordinates should preserve accumulated coords
+testFlattenDEMBareShift :: Test
+testFlattenDEMBareShift = TestList
+  [ "bare shift preserves coords" ~:
+      let input = DEM
+            [ DEMInstrShift (DEMShift [1.0, 0.5] 0)
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0, 0.0])
+            , DEMInstrShift (DEMShift [] 10)
+            , DEMInstrDetector (DEMDetector (DetectorId 1) [0.0, 0.0])
+            ]
+          expected = DEM
+            [ DEMInstrDetector (DEMDetector (DetectorId 0) [1.0, 0.5])
+            , DEMInstrDetector (DEMDetector (DetectorId 11) [1.0, 0.5])
+            ]
+      in expected ~=? flattenDEM input
+
+  , "bare shift only det id" ~:
+      let input = DEM
+            [ DEMInstrShift (DEMShift [] 5)
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [2.0, 3.0])
+            ]
+          expected = DEM
+            [ DEMInstrDetector (DEMDetector (DetectorId 5) [2.0, 3.0])
+            ]
+      in expected ~=? flattenDEM input
+
+  , "mixed coord and bare shifts" ~:
+      let input = DEM
+            [ DEMInstrShift (DEMShift [1.0] 0)
+            , DEMInstrShift (DEMShift [] 5)
+            , DEMInstrShift (DEMShift [2.0] 0)
+            , DEMInstrDetector (DEMDetector (DetectorId 0) [0.0])
+            ]
+          expected = DEM
+            [ DEMInstrDetector (DEMDetector (DetectorId 5) [3.0])
             ]
       in expected ~=? flattenDEM input
   ]
diff --git a/test/Test/DEM/Parse.hs b/test/Test/DEM/Parse.hs
--- a/test/Test/DEM/Parse.hs
+++ b/test/Test/DEM/Parse.hs
@@ -1,6 +1,7 @@
 module Test.DEM.Parse where
 
 import Test.HUnit
+import Text.Megaparsec (runParser)
 
 import StimParser.DEM.Expr
 import StimParser.DEM.Parse
@@ -27,6 +28,8 @@
   , testParseDEMScientificCoords
   , testParseDEMMixedTargets
   , testParseDEMEmpty
+  , testParseDEMCaret
+  , testParseDEMEofEnforced
   ]
 
 -- | Parse individual error instructions
@@ -111,6 +114,18 @@
   , "shift with det offset" ~:
       DEMShift [0.0, 1.0] 10
       ~=? run parseDEMShift "shift_detectors(0, 1) 10"
+
+  , "shift bare no coords" ~:
+      DEMShift [] 96
+      ~=? run parseDEMShift "shift_detectors 96"
+
+  , "shift bare zero" ~:
+      DEMShift [] 0
+      ~=? run parseDEMShift "shift_detectors 0"
+
+  , "shift single coord no coords" ~:
+      DEMShift [0.5] 1
+      ~=? run parseDEMShift "shift_detectors(0.5) 1"
   ]
 
 -- | Parse repeat blocks
@@ -264,4 +279,78 @@
 testParseDEMEmpty = TestList
   [ "empty string is empty dem" ~:
       DEM [] ~=? run parseDEM ""
+  ]
+
+-- | Caret (^) rejection — '^' indicates unflattened DEM decompositions,
+-- which are not part of the stable DEM format. We reject them loudly
+-- instead of silently discarding them.
+testParseDEMCaret :: Test
+testParseDEMCaret = TestList
+  [ "single caret fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D0 ^ D1 L0"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "parser should fail on unflattened DEM with ^"
+
+  , "multiple carets fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D0 ^ D1 ^ D2 L0"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "parser should fail on unflattened DEM with ^"
+
+  , "caret between detectors fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D0 ^ D1 ^ D2"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "parser should fail on unflattened DEM with ^"
+
+  , "caret with observables fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D0 ^ L0 ^ D1"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "parser should fail on unflattened DEM with ^"
+
+  , "caret at start fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) ^ D0 D1"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "parser should fail on unflattened DEM with ^"
+
+  , "caret at end fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D0 D1 ^"
+      case result of
+        Left _ -> return ()
+        Right _ -> assertFailure "parser should fail on unflattened DEM with ^"
+
+  , "no-caret error still parses" ~:
+      DEM [DEMInstrError (DEMError 0.01 [TargetDetector (DetectorId 0), TargetDetector (DetectorId 1), TargetObservable (ObservableId 0)])]
+      ~=? run parseDEM "error(0.01) D0 D1 L0"
+  ]
+
+-- | EOF enforcement — parseDEM must consume all input
+testParseDEMEofEnforced :: Test
+testParseDEMEofEnforced = TestList
+  [ "invalid trailing text fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D0 invalid_trailing"
+      case result of
+        Left _ -> return ()  -- expected to fail
+        Right _ -> assertFailure "parser should fail on unconsumed input"
+
+  , "invalid token after caret fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D0 ^ @#$"
+      case result of
+        Left _ -> return ()  -- expected to fail
+        Right _ -> assertFailure "parser should fail on invalid token after caret"
+
+  , "partial detector id fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01) D"
+      case result of
+        Left _ -> return ()  -- expected to fail
+        Right _ -> assertFailure "parser should fail on partial detector id"
+
+  , "unclosed parenthesis fails" ~: TestCase $ do
+      let result = runParser parseDEM "" "error(0.01"
+      case result of
+        Left _ -> return ()  -- expected to fail
+        Right _ -> assertFailure "parser should fail on unclosed parenthesis"
   ]
diff --git a/test/Test/Parse.hs b/test/Test/Parse.hs
--- a/test/Test/Parse.hs
+++ b/test/Test/Parse.hs
@@ -24,6 +24,7 @@
   , testParseAnnTy
   , testParseFInd
   , testParseAnn
+  , testParseStimNoiseOrder
   ]
 
 -- Helper to compare using show (since types don't derive Eq)
@@ -270,4 +271,33 @@
   , assertShowEqual "parseAnn OBSERVABLE_INCLUDE" 
       (Ann OBSERVABLE_INCLUDE Nothing [In 0] [QRec (Rec (-1))]) 
       (run parseAnn "OBSERVABLE_INCLUDE(0) rec[-1]")
+  ]
+
+-- | Regression test for the noise-before-gate ordering bug.
+-- Noise-channel names such as X_ERROR share a prefix with gate names (X, Y,
+-- Z, I). The top-level 'parseStim' dispatcher must try noise first, otherwise
+-- the gate parser consumes the leading letter and succeeds with an empty
+-- target list, leaving the rest of the line unparsed.
+testParseStimNoiseOrder :: Test
+testParseStimNoiseOrder = TestList
+  [ assertShowEqual "parseStim X_ERROR"
+      (StimList [StimNoise (NoiseNormal X_ERROR Nothing Nothing [0.5] [Q 0])])
+      (run parseStim "!!!Start\nX_ERROR(0.5) 0\n")
+
+  , assertShowEqual "parseStim Y_ERROR"
+      (StimList [StimNoise (NoiseNormal Y_ERROR Nothing Nothing [0.01] [Q 1])])
+      (run parseStim "!!!Start\nY_ERROR(0.01) 1\n")
+
+  , assertShowEqual "parseStim Z_ERROR"
+      (StimList [StimNoise (NoiseNormal Z_ERROR Nothing Nothing [0.02] [Q 2])])
+      (run parseStim "!!!Start\nZ_ERROR(0.02) 2\n")
+
+  , assertShowEqual "parseStim I_ERROR"
+      (StimList [StimNoise (NoiseNormal I_ERROR Nothing Nothing [0.1] [Q 0])])
+      (run parseStim "!!!Start\nI_ERROR(0.1) 0\n")
+
+  -- Sanity check: ordinary gate lines still work after the reordering.
+  , assertShowEqual "parseStim H gate"
+      (StimList [StimG (Gate H Nothing [Q 0, Q 1, Q 2])])
+      (run parseStim "!!!Start\nH 0 1 2\n")
   ]
