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;