diff --git a/Changelog.markdown b/Changelog.markdown
--- a/Changelog.markdown
+++ b/Changelog.markdown
@@ -1,3 +1,7 @@
+# 2026-02-16 (v1.2.4)
+
+* fix bug resulting in duplicate rows after group operator application
+
 # 2026-02-09 (v1.2.3)
 
 * add support for importing whole Haskell modules for easier application logic programming
diff --git a/examples/zoo.hs b/examples/zoo.hs
--- a/examples/zoo.hs
+++ b/examples/zoo.hs
@@ -11,7 +11,6 @@
 import Data.Time.Calendar
 import qualified Data.Text.IO as T
 
---TODO add dbc func ACLs
 data Ticket = Ticket
   { ticketId   :: Integer
   , visitorAge :: Integer    -- years
diff --git a/project-m36.cabal b/project-m36.cabal
--- a/project-m36.cabal
+++ b/project-m36.cabal
@@ -1,6 +1,6 @@
 Cabal-Version: 2.2
 Name: project-m36
-Version: 1.2.3
+Version: 1.2.4
 License: MIT
 --note that this license specification is erroneous and only labeled MIT to appease hackage which does not recognize public domain packages in cabal >2.2- Project:M36 is dedicated to the public domain
 Build-Type: Simple
diff --git a/src/lib/ProjectM36/Streaming/RelationalExpression.hs b/src/lib/ProjectM36/Streaming/RelationalExpression.hs
--- a/src/lib/ProjectM36/Streaming/RelationalExpression.hs
+++ b/src/lib/ProjectM36/Streaming/RelationalExpression.hs
@@ -484,7 +484,7 @@
               case projectionAttributesForNames groupAttrNames attrsIn of
                 Left err -> pure (Left err)
                 Right groupProjectionAttributes -> do
-                  eGroupS <- executePlan (ProjectTupleStreamPlan nonGroupProjectionAttributes expr () orig) ctxTuples gfEnv cacheKeyBlackList cache
+                  eGroupS <- executePlan (UniqueifyTupleStreamPlan (ProjectTupleStreamPlan nonGroupProjectionAttributes expr () orig) ()) ctxTuples gfEnv cacheKeyBlackList cache
                   case eGroupS of
                     Left err -> pure (Left err)
                     Right (StreamRelation _ nonGroupProjectionTupS) -> do
diff --git a/test/TutorialD/InterpreterTest.hs b/test/TutorialD/InterpreterTest.hs
--- a/test/TutorialD/InterpreterTest.hs
+++ b/test/TutorialD/InterpreterTest.hs
@@ -102,7 +102,8 @@
       testComplexTypeVarResolution,
       testNotifications,
       testDBCFunctionAccessControl,
-      testAttributeTypeHintsFromExistingRelVar
+      testAttributeTypeHintsFromExistingRelVar,
+      testGroup
       ]
 
 simpleRelTests :: Test
@@ -959,3 +960,31 @@
   let err1 = "RelationTypeMismatchError"
   expectTutorialDErr session dbconn (T.isPrefixOf err1) "x:=relation{tuple{a 3, b 4}}"
 
+-- ensure that non-group attributes are not duplicates after the projection
+testGroup :: Test
+testGroup = TestCase $ do
+  (session, dbconn) <- dateExamplesConnection emptyNotificationCallback
+  executeTutorialD session dbconn "x := s group ({sname,status,s#} as rel)"
+  result <- executeRelationalExpr session dbconn (RelationVariable "x" ())
+  
+  let expected = mkRelationFromList attrs
+                 [[t "Paris",
+                    mkSubRel [[t "S2", t "Jones", i 10],
+                              [t "S3", t "Blake", i 30]]],
+                   [t "London",
+                     mkSubRel [[t "S4", t "Clark", i 20],
+                               [t "S1", t "Smith", i 20]]],
+                   [t "Athens",
+                     mkSubRel [[t "S5", t "Adams", i 30]]]
+                 ]
+      attrs = A.attributesFromList [Attribute "city" TextAtomType,
+                                    Attribute "rel" (RelationAtomType subAttrs)]
+      subAttrs = A.attributesFromList [Attribute "s#" TextAtomType,
+                                       Attribute "sname" TextAtomType,
+                                       Attribute "status" IntegerAtomType]
+      t = TextAtom
+      i = IntegerAtom
+      mkSubRel atoms = case mkRelationFromList subAttrs atoms of
+                         Left err -> error $ "bad mkSubRel " <> show err
+                         Right rel -> RelationAtom rel
+  assertEqual "group operator" expected result
