be5cbe094825357e026912debc79944cc15bacde
hiram
  Sun Sep 27 12:58:52 2026 -0700
claude review fixing bugs better safety on database assembly mash sketch archive refs #34360

diff --git src/hg/utils/automation/AssemblyDivergence.pm src/hg/utils/automation/AssemblyDivergence.pm
index 07ca47bb6f1..0ec5164f10c 100644
--- src/hg/utils/automation/AssemblyDivergence.pm
+++ src/hg/utils/automation/AssemblyDivergence.pm
@@ -91,32 +91,42 @@
   my $base = basename($path);
   $base =~ s/\.(2bit|fa|fasta)(\.gz)?$//;
   return $base;
 }
 
 # sketch($seq, $workDir, $tag, $regenerate) -> path to a .msh file for $seq.
 #   $seq can be an existing sequence file path, a bare GenArk accession
 #   (e.g. GCA_939628115.1), or a bare UCSC database name (e.g. hg38).
 #
 #   The persistent mashSketch/ cache -- and everything needed to find
 #   it -- lives entirely under $HgAutomate::clusterData
 #   (/hive/data/genomes/), which is reachable from every cluster node.
 #   This deliberately never touches /gbdb/ or hgcentraltest: cluster
 #   jobs can't reach hgwdev (where hgcentraltest lives) and shouldn't
 #   try, and /gbdb isn't mounted on cluster nodes at all.  So:
+#     - a GenArk accession only qualifies for the persistent cache when
+#       mashSketchDir() finds a real on-disk <asmId>_* build directory;
+#       a UCSC db name only qualifies when $HgAutomate::clusterData/$db
+#       is a real directory.  Both are pure filesystem checks (no ssh),
+#       and both exist so an arbitrary local file that merely happens to
+#       share a real assembly's basename (e.g. a stray /tmp/hg38.2bit)
+#       can't get cached into -- and silently corrupt -- that assembly's
+#       real, shared cache.  Anything not backed by a real build/db
+#       directory falls through to the -workDir one-off sketch instead.
 #     - a GenArk accession or UCSC db name with an already-cached .msh
-#       is a pure clusterData filesystem check -- always works, anywhere.
+#       is then just a pure clusterData filesystem check -- always
+#       works, anywhere.
 #     - on a cache MISS, this only proceeds if $seq already IS a real,
 #       existing sequence file (i.e. the caller resolved it themselves,
 #       e.g. to a GenArk build-tree .2bit under clusterData, or handed a
 #       literal /gbdb/... path from somewhere that does have it mounted)
 #       -- a bare name with no cache and no real file in hand just fails
 #       (see the final croak below), it never goes looking for one.
 #
 #   Cached sketches accumulate once per assembly and are shared by
 #   every future comparison involving it, cluster run or standalone
 #   mashDistance.pl check alike.  Anything that isn't a GenArk accession
 #   or a recognized UCSC db falls back to a one-off sketch named
 #   mashSketch.$tag.msh under $workDir, as before.
 sub sketch {
   my ($seq, $workDir, $tag, $regenerate) = @_;
   my $prefix;
@@ -143,31 +153,38 @@
         my $buildDir = $cacheDir;
         $buildDir =~ s#/mashSketch$##;
         my $builtSeq = "$buildDir/" . basename($buildDir) . ".2bit";
         if (-e $builtSeq) {
           make_path($cacheDir) if (! -d $cacheDir);
           $prefix = "$cacheDir/$accession";
           $id = $accession;
           $srcSeq = $builtSeq;
         }
       }
     }
   }
 
   if (! $prefix) {
     my $db = &seqBaseName($seq);
-    if ($db ne '') {
+    # Require a real /hive/data/genomes/$db directory before trusting
+    # $db as an actual UCSC assembly -- pure filesystem check, mirroring
+    # mashSketchDir()'s build-directory requirement for GenArk above.
+    # Without this, any existing sequence file whose basename happens to
+    # match a real db name (e.g. a stray local /tmp/hg38.2bit passed via
+    # -target2Bit) would get cached straight into -- and silently
+    # corrupt -- that db's real, shared production cache.
+    if ($db ne '' && -d "$HgAutomate::clusterData/$db") {
       my $cacheDir = "$HgAutomate::clusterData/$db/mashSketch";
       if (! $regenerate && -e "$cacheDir/$db.msh") {
         return "$cacheDir/$db.msh";   # cache hit -- pure filesystem, done
       }
       # Not cached yet -- only proceed with a source we already have in
       # hand (see the sub's header comment above); never search for one.
       if (-e $srcSeq) {
         make_path($cacheDir) if (! -d $cacheDir);
         $prefix = "$cacheDir/$db";
         $id = $db;
       }
     }
   }
 
   if (! $prefix) {