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/inc/snapshotSession.h src/hg/inc/snapshotSession.h
index 9bb0d1b967f..e5b07c9897f 100644
--- src/hg/inc/snapshotSession.h
+++ src/hg/inc/snapshotSession.h
@@ -29,35 +29,41 @@
 /* Reserved userName for logged-out (anonymous) snapshots, matching doSaveSessionJson's convention
  * and the /s/l/<name> short link. */
 #define snapshotAnonUser "l"
 
 /* Default cleaner TTL: an anonymous snapshot not opened within this many days is deleted.
  * 4 years ~ the length of a typical PhD, so a link in a thesis keeps working for its author's degree.
  * Override with the hg.conf setting "snapshot.ttlDays". */
 #define snapshotDefaultTtlDays (4 * 365)
 
 struct snapshotType
 /* A registered kind of shareable view snapshot: the cart variables a given feature needs to
  * reconstruct one of its views.  Register one per feature and keep the list minimal. */
     {
     char *name;         /* type key sent by the client, e.g. "blat" */
     char **vars;        /* NULL-terminated cart variable names to persist (besides "db") */
+    char *requiredVar;  /* if non-NULL, the snapshot is a dead link without this cart var, so the
+                         * save is rejected when it is absent (e.g. results not built yet) */
     };
 
 struct snapshotType *snapshotTypeFind(char *name);
 /* Return the registered snapshot type, or NULL if name is not a known type. */
 
+boolean snapshotHasRequired(struct snapshotType *type, struct cart *cart);
+/* Return FALSE when type declares a requiredVar that is missing/empty in cart (saving it would make
+ * a link that reopens to nothing), otherwise TRUE. */
+
 boolean snapshotIsSnapshotName(char *sessionName);
 /* Return TRUE if sessionName is a snapshot name (starts with the "__" prefix). */
 
 char *snapshotNewName(struct sqlConnection *conn, char *encUserName);
 /* Alloc and return a fresh "__"-prefixed snapshot name, server-generated and checked against
  * namedSessionDb so it is guaranteed unique for encUserName (share tokens must never collide and
  * overwrite each other).  The token is long (128 bits) and URL-safe, so it needs no CGI-encoding. */
 
 int saveSnapshotSession(struct sqlConnection *conn, char *snapshotTypeName,
                         char *encUserName, char *encSessionName, struct cart *cart);
 /* Save a minimal shared-by-link session named encSessionName (which must already start with "__")
  * under encUserName, holding only the variables declared by snapshotTypeName (plus "db"), and moving
  * just those variables' trash files into durable sessionData storage when it is configured.
  * Overwrites any existing row of that name, preserving its firstUse/useCount.  errAborts on an
  * unknown type or a name lacking the "__" prefix.  Returns the (post-increment) useCount. */