75e828960283291546d2c1a27845e2cf3823adcd
max
  Mon Sep 14 05:29:02 2026 -0700
uniprot otto: the miniprot cluster job needs absolute paths

GRCz12ab failed with the parasol job crashing four times, return 1, no output.
The wrapper I wrote ran

miniprot -t 16 --gff protToGenome/GRCz12ab/.../genome.fa fasta/7955.fa > $1

and a parasol job runs with its working directory set to the batch directory, not
to the directory the pipeline runs in, so neither input existed from the job's
point of view. The BLAST batch next door gets away with relative paths because it
cds into its own workdir and its jobList is written relative to that; this batch
directory sits a level deeper and its paths were relative to the otto root.

Every path in the wrapper, the jobList command and the output check is now
absolute.

Verified on the cluster against the real 1.48 Gb zebrafish genome: successful
batch, a 195 MB GFF with 93518 mRNA records.

refs #38300

diff --git src/hg/utils/otto/uniprot/doUniprot src/hg/utils/otto/uniprot/doUniprot
index c017c44c890..97b2abd17d8 100755
--- src/hg/utils/otto/uniprot/doUniprot
+++ src/hg/utils/otto/uniprot/doUniprot
@@ -1544,39 +1544,44 @@
     return max(gb, 8)
 
 def runMiniprotOnCluster(db, genomeFa, protFa, gffName, workDir):
     """ run one miniprot job on the parasol cluster.
     It has to be told both numbers: para's default RAM is the node's RAM divided by its CPU
     count, which for a 16 CPU job is far less than miniprot needs, and without -cpu parasol
     would pack more of these onto a node than it has cores for.
     """
     ram = miniprotRamGb(genomeFa)
     jobDir = join(workDir, "cluster")
     if not isdir(jobDir):
         os.makedirs(jobDir)
 
     # a wrapper, so the jobList line stays free of the redirection and quoting that
     # parasol's job parser does not accept
+    # Every path here has to be absolute. A parasol job runs with its working directory
+    # set to the batch directory, not to the directory the pipeline runs in, so a path
+    # like "fasta/7955.fa" simply does not exist from the job's point of view and miniprot
+    # exits without writing anything.
     jobSh = join(jobDir, "runMiniprot.sh")
     with open(jobSh, "w") as ofh:
         ofh.write("#!/bin/sh\nset -e\n")
-        ofh.write("%s -t %d --gff %s %s > $1\n" % (miniprotBin, miniprotThreads, genomeFa, protFa))
+        ofh.write("%s -t %d --gff %s %s > $1\n" % \
+                (miniprotBin, miniprotThreads, abspath(genomeFa), abspath(protFa)))
     os.chmod(jobSh, 0o755)
 
     jobList = join(jobDir, "jobList")
     with open(jobList, "w") as ofh:
-        ofh.write("%s {check out exists %s}\n" % (jobSh, gffName))
+        ofh.write("%s {check out exists %s}\n" % (abspath(jobSh), abspath(gffName)))
 
     logging.info("%s: miniprot on the cluster, -cpu=%d -ram=%dg" % (db, miniprotThreads, ram))
     run("cd %s && para make -cpu=%d -ram=%dg jobList" % (jobDir, miniprotThreads, ram))
 
 def miniprotProteins(fullFaFname, db, mapFname, stats):
     """ align the UniProt proteins straight to the genome with miniprot and write a PSL.
     Used when the assembly has no gene models worth mapping through. This replaced both
     "blat -q=prot" and mapping through Augustus: Augustus is ab initio, so going through it
     stacks its errors on top of ours, and the BLAT protein search is very slow on a big
     genome.
     """
     workDir = mapFname+".miniprot.tmp"
     if not isdir(workDir):
         os.makedirs(workDir)