0cfb3c37fdb1de8873336f9d6f95b3422a465964
hiram
  Sun Aug 9 10:45:26 2026 -0700
in the case of an equivalent available browser for a user browser build request, alert the user to the existence of the equivalent refs #31811

diff --git src/hg/hubApi/findGenome.c src/hg/hubApi/findGenome.c
index 0025898f646..ee2e3a5c772 100644
--- src/hg/hubApi/findGenome.c
+++ src/hg/hubApi/findGenome.c
@@ -538,77 +538,101 @@
     jsonWriteNumber(jw, "availableAssemblies", universeCount);
     if (totalMatchCount > itemCount)
 	jsonWriteBoolean(jw, "maxItemsLimit", TRUE);
     apiFinishOutput(0, NULL, jw);
     }
 else
     apiErrAbort(err400, err400Msg, "no genomes found matching search term %s='%s' for endpoint: /findGenome", argQ, inputSearchString);
 
 hDisconnectCentral(&conn);
 
 }
 
 void apiAssemblyRequest(char *words[MAX_PATH_INFO])
 /* interface to the assemblySearch.html request form.  Replaces the
  *   primitive /cgi-bin/asr perl CGI script.  Inserts a row into the
- *   ottoRequest table; the otto cron job watcher handles email.
+ *   ottoRequest table; the otto cron job watcher handles email.  When
+ *   asmAlias already maps the requested asmId to an existing browser,
+ *   that db goes into toDb instead of a duplicate asmId, which tells
+ *   ottoRequest.py to send an "already available" acknowledgement
+ *   instead of queuing a build.
  */
 {
 char *extraArgs = verifyLegalArgs(argAssemblyRequest);
 if (extraArgs)
     apiErrAbort(err400, err400Msg, "extraneous arguments found for function /assemblyRequest '%s'", extraArgs);
 
 char *asmId      = cgiOptionalString(argAsmId);
 char *name       = cgiOptionalString(argName);
 char *email      = cgiOptionalString(argEmail);
 char *betterName = cgiOptionalString(argBetterName);
 char *comment    = cgiOptionalString(argComment);
 
 if (isEmpty(asmId) || isEmpty(name) || isEmpty(email))
     apiErrAbort(err400, err400Msg, "must have arguments: %s, %s, %s for endpoint '/assemblyRequest'", argAsmId, argName, argEmail);
 
 /* Require a session cookie.  Robots that have not
  *   passed the challenge will not have one. */
 char *cookieName = hUserCookie();
 char *userId = findCookieData(cookieName);
 if (isEmpty(userId))
     apiErrAbort(err400, err400Msg, "can not find required inputs for endpoint '/assemblyRequest'");
 
 /* the ottoRequest table has no name/betterName columns, fold them into comment */
 struct dyString *fullComment = dyStringNew(0);
 dyStringPrintf(fullComment, "name: '%s'", name);
 if (isNotEmpty(betterName))
     dyStringPrintf(fullComment, "; betterName: '%s'", betterName);
 if (isNotEmpty(comment))
     dyStringPrintf(fullComment, "; comment: '%s'", comment);
 
-/* Record the request in the ottoRequest table (asmId placed in both fromDb
- * and toDb in case toDb does not allow empty).  Done locally (this host has
- * hgcentral write grants) or relayed to genome.ucsc.edu (it doesn't) -- see
- * inUcscEduDomain()/onGenomeRRMachine(). */
+/* If asmAlias already maps this asmId to a browser we have, there is
+ * nothing to build.  asmAliasFind() returns its argument unchanged when
+ * no alias row matches, so differentString() is the standard way (see
+ * hubApi.c and hg/lib/web.c) to detect a real hit. */
+char *existingBrowser = asmAliasFind(asmId);
+boolean alreadyExists = differentString(existingBrowser, asmId);
+
+/* Record the request in the ottoRequest table.  For a normal request,
+ * asmId is placed in both fromDb and toDb (toDb otherwise unused for
+ * requestType='assembly', and left non-empty in case the column does
+ * not allow empty).  When asmAlias resolves asmId to an existing
+ * browser, toDb carries that browser's db/asmId instead -- fromDb !=
+ * toDb is then the signal that ottoRequest.py uses to send the "already
+ * available" acknowledgement and close out the row immediately instead
+ * of handing it to asmRequestWatch.sh, which would otherwise wait
+ * forever for a build that will never happen.  Done locally (this host
+ * has hgcentral write grants) or relayed to genome.ucsc.edu (it
+ * doesn't) -- see inUcscEduDomain()/onGenomeRRMachine(). */
+char *toDb = alreadyExists ? existingBrowser : asmId;
 char *ottoStatus;
 if (inUcscEduDomain() && !onGenomeRRMachine())
-    ottoStatus = relaySubmitOttoRequest("assembly", asmId, asmId, email, dyStringContents(fullComment));
+    ottoStatus = relaySubmitOttoRequest("assembly", asmId, toDb, email, dyStringContents(fullComment));
 else
-    ottoStatus = submitOttoRequest("assembly", asmId, asmId, email, dyStringContents(fullComment));
+    ottoStatus = submitOttoRequest("assembly", asmId, toDb, email, dyStringContents(fullComment));
 
 if (sameString(ottoStatus, "error"))
     apiErrAbort(err500, err500Msg, "internal error recording assembly request");
 
 char nowTime[256];
 time_t seconds = clock1();
 struct tm *timeNow = localtime(&seconds);
 strftime(nowTime, sizeof nowTime, "%Y-%m-%d %H:%M:%S", timeNow);
 
 struct dyString *msg = dyStringNew(0);
 dyStringPrintf(msg, "%s\nAssembly request\nasmId: %s\nname: %s\nemail: %s\nbetterName: %s\ncomment: %s",
     nowTime, asmId, name, email,
     isNotEmpty(betterName) ? betterName : "",
     isNotEmpty(comment) ? comment : "");
+if (alreadyExists)
+    dyStringPrintf(msg, "\nnote: an equivalent browser for '%s' already exists as '%s'; "
+        "a confirmation email with a link is on its way instead of a new build",
+        asmId, existingBrowser);
 
 struct jsonWrite *jw = apiStartOutput();
 jsonWriteString(jw, "msg", dyStringContents(msg));
+jsonWriteString(jw, "existingBrowser", alreadyExists ? existingBrowser : NULL);
 apiFinishOutput(0, NULL, jw);
 
 dyStringFree(&fullComment);
 dyStringFree(&msg);
 }       /*    void apiAssemblyRequest(char *words[MAX_PATH_INFO])    */