081dc1a2de216d74a28252747d593c2b1288f251
max
  Tue May 26 02:06:08 2026 -0700
hubApi/botDelay: return JSON 500 when bottleneck server is unreachable

abortAndExplainConnectFail() previously called exit(0) directly after
writing raw HTML, which bypassed every error handler and produced a
broken response for JSON API clients.  Change it to errAbort() so it
can be caught by errCatch.

Wrap both hgBotDelayTimeFrac() call sites in hubApi (the global delay
in main() and the BLAT-specific extra delay in apiBlat()) in errCatch
blocks that call apiErrAbort(500) on failure, giving callers a proper
JSON error rather than an HTML fragment or a silent exit.

refs #36315

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

diff --git src/hg/hubApi/hubApi.c src/hg/hubApi/hubApi.c
index 9a8cdc62f00..a17ef6dc1d8 100644
--- src/hg/hubApi/hubApi.c
+++ src/hg/hubApi/hubApi.c
@@ -1649,31 +1649,38 @@
  * hgBotDelayTimeFrac->getBotCheckString in hg/lib/botDelay.c would
  * hUserAbort() on an invalid key, which apache renders as a 500 because
  * it emits plain HTML rather than the JSON the API contract promises. */
 char *earlyApiKey = cgiOptionalString(argApiKey);
 if (isNotEmpty(earlyApiKey))
     {
     struct sqlConnection *centralConn = hConnectCentralNoCache();
     char *userName = hubSpaceUserNameForApiKey(centralConn, earlyApiKey);
     sqlDisconnect(&centralConn);
     if (isEmpty(userName))
         apiErrAbort(err403, err403Msg,
             "invalid '%s' provided. Make sure the apiKey is valid, or contact us.",
             argApiKey);
     }
 /* similar delay system as in DAS server */
+struct errCatch *bnErrCatch = errCatchNew();
+if (errCatchStart(bnErrCatch))
     botDelay = hgBotDelayTimeFrac(delayFraction);
+errCatchEnd(bnErrCatch);
+if (bnErrCatch->gotError)
+    apiErrAbort(err500, err500Msg, "bottleneck server unavailable: %s",
+        bnErrCatch->message->string);
+errCatchFree(&bnErrCatch);
 if (botDelay > 0)
     {
     if (botDelay > 2000)
         {
 	sleep1000(botDelay);
 	hogExit();
         return 0;
         }
     sleep1000(botDelay);
     }
 
 setGlobalCgiVars();
 
 #ifdef NOTUSED // the argument processing doesn't allow udcTimeout
 int timeout = cgiOptionalInt("udcTimeout", 300);