8bf67a4375f3d5f15a109bea5a33a782c602fe67
braney
  Tue Mar 10 13:07:00 2026 -0700
hubCheck: warn about periods in track names refs #37223

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

diff --git src/hg/utils/hubCheck/hubCheck.c src/hg/utils/hubCheck/hubCheck.c
index 65a6ef37a56..318507c9d9d 100644
--- src/hg/utils/hubCheck/hubCheck.c
+++ src/hg/utils/hubCheck/hubCheck.c
@@ -1094,30 +1094,43 @@
     char *p;
     for (p = name; *p != '\0'; p++)
         {
         if (!isValidSeqNameChar(*p))
             {
             warn("warning: sequence name '%s' in genome '%s' contains invalid character '%c' -"
                 "only [A-Za-z0-9._-] are allowed. Consider using chromAlias for alternative names.",
                 name, genomeName, *p);
             break;
             }
         }
     }
 slFreeList(&seqList);
 }
 
+static void checkTrackNamesForDots(struct trackDb *tdbList)
+/* Warn about track names containing periods before they get polished away. */
+{
+struct trackDb *tdb;
+for (tdb = tdbList; tdb != NULL; tdb = tdb->next)
+    {
+    if (strchr(tdb->track, '.'))
+        warn("warning: track name \"%s\" contains a period which will be changed to an underscore. Periods in track names can cause problems with table browser filters. Consider using underscores instead.", tdb->track);
+    if (tdb->subtracks != NULL)
+        checkTrackNamesForDots(tdb->subtracks);
+    }
+}
+
 int hubCheckGenome(struct trackHub *hub, struct trackHubGenome *genome,
                 struct trackHubCheckOptions *options, struct dyString *errors)
 /* Check out genome within hub. */
 {
 struct errCatch *errCatch = errCatchNew();
 struct trackDb *tdbList = NULL;
 int genomeErrorCount = 0;
 boolean openedGenome = FALSE;
 verbose(3, "checking genome %s\n", trackHubSkipHubName(genome->name));
 
 if (errCatchStart(errCatch))
     {
     if (genome->twoBitPath != NULL)
         {
         // check that twoBitPath is a valid file, warn instead of errAbort so we can continue checking
@@ -1145,30 +1158,31 @@
         char *groupsFile = genome->groups;
         if (groupsFile != NULL && !extFileExists(groupsFile))
             warn("warning: '%s' groups file does not exist or is not accessible: '%s'", genome->name, groupsFile);
 
         char *htmlPath = hashFindVal(genome->settingsHash, "htmlPath");
         if (htmlPath == NULL)
             warn("warning: missing htmlPath setting for assembly hub '%s'", genome->name);
         else if (!extFileExists(htmlPath))
             warn("warning: '%s' htmlPath file does not exist or is not accessible: '%s'", genome->name, htmlPath);
 
         }
     boolean foundFirstGenome = FALSE;
     tdbList = trackHubTracksForGenome(hub, genome, NULL, &foundFirstGenome);
     tdbList = trackDbLinkUpGenerations(tdbList);
     tdbList = trackDbPolishAfterLinkup(tdbList, genome->name);
+    checkTrackNamesForDots(tdbList);
     trackHubPolishTrackNames(hub, tdbList);
     }
 errCatchEnd(errCatch);
 if (errCatch->gotError || errCatch->gotWarning)
     {
     openedGenome = TRUE;
     genomeErr(errors, errCatch->message->string, hub, genome, options->htmlOut);
     if (errCatch->gotError || !options->allowWarnings)
         genomeErrorCount += 1;
     }
 errCatchFree(&errCatch);
 
 verbose(2, "%d tracks in %s\n", slCount(tdbList), genome->name);
 struct trackDb *tdb;
 int tdbCheckVal;