ef8d1004e3d984933a79686a7369d74825886c48
max
  Fri Sep 4 11:35:31 2026 -0700
snapshotSession: reject a snapshot missing its required var

A "blat" snapshot is a dead link without blatLastBigBed (the pinned bigPsl).
That variable is set by an async hgc buildBigPsl call, so a share clicked
before the build finished - or after it failed - would have minted a link
that reopens to nothing.  A snapshotType may now name a requiredVar, and
doSaveSessionJson refuses the save (asking the caller to retry) instead of
handing out a broken link.  Found in code review of the BLAT share wiring.

refs #38197

diff --git src/hg/hgSession/hgSession.c src/hg/hgSession/hgSession.c
index e7babde692a..10d4096e9d1 100644
--- src/hg/hgSession/hgSession.c
+++ src/hg/hgSession/hgSession.c
@@ -1104,35 +1104,43 @@
 
 /* Keep our control variables out of the saved session contents and the user's own cart. */
 cartRemove(cart, hgsDoSaveSessionJson);
 cartRemove(cart, hgsShareAnon);
 cartRemove(cart, hgsFailIfExists);
 cartRemove(cart, hgsSnapshotType);
 cartRemove(cart, hgsNewSessionName);
 cartRemove(cart, hgsNewSessionShare);
 
 /* Snapshot path: a lightweight session holding only the feature's declared cart vars, under a
  * server-generated, guaranteed-unique "__"-prefixed name (share tokens must never collide and
  * silently overwrite one another).  Handled before the normal full-session logic because its
  * naming rules differ.  Works for both anonymous ("l") and logged-in owners. */
 if (isNotEmpty(snapshotType))
     {
-    if (snapshotTypeFind(snapshotType) == NULL)
+    struct snapshotType *st = snapshotTypeFind(snapshotType);
+    if (st == NULL)
         {
         saveSessionJsonError(conn, "Unknown snapshot type.");
         return;
         }
+    /* Refuse to mint a link that would reopen to nothing (e.g. BLAT results not built yet); tell the
+     * caller to retry rather than handing out a dead link. */
+    if (!snapshotHasRequired(st, cart))
+        {
+        saveSessionJsonError(conn, "These results are not ready yet. Please try again in a moment.");
+        return;
+        }
     char *snapUser = anon ? "l" : cgiEncodeFull(userName);
     char *snapName;
     if (isEmpty(sessionName))
         snapName = snapshotNewName(conn, snapUser);            /* server-generated, unique */
     else if (startsWith(snapshotNamePrefix, sessionName))
         snapName = cgiEncodeFull(sessionName);
     else
         snapName = catTwoStrings(snapshotNamePrefix, cgiEncodeFull(sessionName));
     saveSnapshotSession(conn, snapshotType, snapUser, snapName, cart);
     char *snapDecoded = cgiDecodeClone(snapName);
     saveSessionJsonResult(conn, snapUser, snapName, snapDecoded);
     return;
     }
 
 char *encUserName = NULL;