8954c29282f84de8f41c183a4cbb2817fbbb87c5
max
  Mon Sep 28 13:53:57 2026 -0700
uniprot otto: an empty cached mapping means nothing aligned, not a broken file, refs #38300

diff --git src/hg/utils/otto/uniprot/doUniprot src/hg/utils/otto/uniprot/doUniprot
index e0b65d36ad3..b0a0b4b6b8b 100755
--- src/hg/utils/otto/uniprot/doUniprot
+++ src/hg/utils/otto/uniprot/doUniprot
@@ -2350,31 +2350,39 @@
         allMd5s.append(pairMd5)
         stats["pairMd5"] = pairMd5[:10]
         stats["pairFname"] = selectFname
 
     # if either the protein sequences, the transcripts, their alignment, the
     # protein/transcript mapping or the alignment threshold changes -> create a new PSL
     # mapping file. minAli belongs in here: it decides which alignments survive, so a
     # mapping built at one threshold must not be silently reused at another. That is
     # exactly what happened after the MINALI global leaked across threads. refs #38300
     fullMd5 = listMd5(allMd5s)[:10]
 
     mapFname = join(mapDir, "%(geneTable)s_%(fullMd5)s.psl" % locals())
 
     if isfile(mapFname) and not doForce:
         logging.info("%s already exists, not rebuilding the protein -> genome mapping PSL" % mapFname)
-        assert(os.path.getsize(mapFname)!=0)
+        # An empty mapping is a real answer, not a broken file: an organism UniProt has two
+        # proteins for, neither of which aligns, produces one. The caller already treats a
+        # freshly built empty mapping that way and carries on, but reusing a cached one used to
+        # assert here, so such an assembly failed on every run after the first. The golden
+        # eagle did, until this. refs #38300
+        if os.path.getsize(mapFname) == 0:
+            logging.warning("%s: the cached protein -> genome mapping is empty, nothing aligns "
+                    "for this assembly" % db)
+            return mapFname, geneTable, fullMd5, protMapSource, protToTrans, False
         # The lift info describes the mapping, so it has to exist wherever the mapping
         # does. A run that was interrupted between building the PSL and writing this file
         # leaves the PSL behind on its own, and the next run then reuses the PSL and falls
         # over copying the file that was never written. Write it here too. refs #38300
         if not isfile(mapDescFname):
             logging.info("%s is missing next to the reused mapping, writing it" % mapDescFname)
             writeMapDesc(stats, db, geneTable, mapDescFname)
         # careful: if you modify this, also modify the other return statement below
         return mapFname, geneTable, fullMd5, protMapSource, protToTrans, False
 
     stats["protMd5"] = protMd5[:10]
     stats["metaMd5"] = metaMd5[:10]
     stats["transMd5"] = transMd5[:10]
     stats["fullMd5"] = fullMd5[:10]
     stats["mapFname"] = basename(mapFname)