diff --git a/python/graph_list_table_model.py b/python/graph_list_table_model.py
index 1dfc34cabd5e42dd8c8122d02f7511f8fc8a810a..338ca5c42ad7ed7b638e5c0e00b58371dc8d2e6e 100644
--- a/python/graph_list_table_model.py
+++ b/python/graph_list_table_model.py
@@ -1,5 +1,6 @@
 from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex
 from mp_code_parser import mp_code_say_headers, say_info
+from message_popup import message_popup
 
 # graph list model column constants
 GRAPH_COLUMN = 0 # the graph item
@@ -29,7 +30,8 @@ class GraphListTableModel(QAbstractTableModel):
       * dataChanged - call this to reflect change into views
     """
 
-    def __init__(self):
+    def __init__(self, parent_window):
+        self.parent_window = parent_window
         self.headers = list()
         super().__init__()
 
@@ -37,18 +39,27 @@ class GraphListTableModel(QAbstractTableModel):
         self.headers = list()
 
     def set_graph_list(self, graphs, mp_code):
-        self.beginResetModel()
 
-        # set the data
-        self.graphs = graphs
+        # calculate headers from SAY statements
+        say_headers, warnings = mp_code_say_headers(mp_code)
 
-        # set the headers
-        say_headers = mp_code_say_headers(mp_code)
+        # change the model's data and header titles
+        self.beginResetModel()
+        self.graphs = graphs
         self.headers = _HARDCODED_HEADERS.copy()
         self.headers.extend(say_headers)
-
         self.endResetModel()
 
+        # maybe show warnings
+        if warnings:
+            message = "SAY statements containing both variables and text " \
+                      "with embedded numbers " \
+                      "cannot be used when sorting traces by attribute.  " \
+                      "Columns will not be provided for SAY events " \
+                      "generated from the following SAY text:\n\n%s" \
+                      %("\n\n".join(warnings))
+            message_popup(self.parent_window, message)
+
     def headerData(self, section, orientation,
                                    role=Qt.ItemDataRole.DisplayRole):
         if role == Qt.ItemDataRole.DisplayRole \
diff --git a/python/gui_manager.py b/python/gui_manager.py
index 34948c60a44ccb3b92d96879dcef16d2e83af1b6..502127301e346a109e881a6f53e26c040194e960 100644
--- a/python/gui_manager.py
+++ b/python/gui_manager.py
@@ -123,7 +123,7 @@ class GUIManager(QObject):
                                          self.spellcheck_whitelist_manager)
 
         # the graph list table model
-        self.graph_list_table_model = GraphListTableModel()
+        self.graph_list_table_model = GraphListTableModel(self.w)
 
         # the graphs manager
         self.graphs_manager = GraphsManager(self.graph_list_table_model,
diff --git a/python/mp_code_parser.py b/python/mp_code_parser.py
index e8ca8e36759ddb6551b9327739ebca51f3eb9544..76ee511c30ec50ff7688887d14cde5a3ec301dae 100644
--- a/python/mp_code_parser.py
+++ b/python/mp_code_parser.py
@@ -243,15 +243,18 @@ def _say_text_and_number_count(say_line):
 
     return quoted_text, number_count
 
+# Return header list and warning list
 # derive sorted say headers from mp_code_text
 # We do not cache these results.  This mp_code_text is from trace generation
 # and is called by graph_list_table_model when the graph is loaded.
+_number_pattern = re.compile(r"[0-9.]+")
 def mp_code_say_headers(mp_code_text):
 
     # convert text into lines without comments
     non_comment_lines = _non_comment_lines(mp_code_text)
 
     header_list = list()
+    warnings = list()
     for line in non_comment_lines:
         non_quote_line = _non_quote_line(line)
 
@@ -264,8 +267,15 @@ def mp_code_say_headers(mp_code_text):
         if _SAY_EXEMPTION_EXPRESSION.globalMatch(non_quote_line).hasNext():
             continue
 
+        # parse out say_text and number count
         say_text, number_count = _say_text_and_number_count(line)
 
+        # say_text must not contain numbers else warn and drop
+        if number_count >= 1 and _number_pattern.findall(say_text):
+            say_content = _SAY_CONTENT.match(line).captured(1)
+            warnings.append(say_content)
+            continue
+
         # add to texts and columns
         if number_count == 0:
             header_list.append(say_text)
@@ -274,11 +284,10 @@ def mp_code_say_headers(mp_code_text):
                 header_list.append("%s (%d)"%(say_text, i+1))
 
     header_list = sorted(header_list, key=str.casefold)
-    return header_list
+    return header_list, warnings
 
 # Return text for the matching column else X or checkmark
 # Say nodes are type="T"."""
-_number_pattern = re.compile(r"[0-9.]+")
 def say_info(gry_graph, column_title):
     if not "trace" in gry_graph:
         raise RuntimeError("bad")