35c5facfb9e64030b6eeb70aadd2026bd9296c21
braney
  Wed Sep 30 17:11:59 2026 -0700
hgSession: a Save request without the share flag or the name no longer ends in a stack dump, refs #38456

doNewSession() read the session name with cartString() and the share flag with
cartBoolean(), and both abort when the variable is missing.  The Save form always
sends both, so only a hand-made request reached this.  doReSaveSession() read the
name the same way, and outDefaultTracks() read db the same way, which a brand-new
cart does not have.

The name and db now have defaults, and a missing name gets the existing "without a
name" message.  For the share flag the code asks the request, not the cart, because
the cart keeps hgS_newSessionShare on purpose.  When the request carries neither the
flag nor the checkbox's shadow, an existing session keeps its own sharing level and a
new one is saved private.

diff --git src/hg/hgSession/hgSession.c src/hg/hgSession/hgSession.c
index 86f6b239a04..59cc28fda19 100644
--- src/hg/hgSession/hgSession.c
+++ src/hg/hgSession/hgSession.c
@@ -858,31 +858,31 @@
 for(; statusList; statusList = statusList->next)
     {
     if (dy)
         dyStringPrintf(dy,"%d=%s ", statusList->id, cgiEncode(statusList->hubUrl));
     else
         printf("%d=%s ", statusList->id, statusList->hubUrl);
     }
 if (dy == NULL)
     printf("\n");
 }
 
 static void outDefaultTracks(struct cart *cart, struct dyString *dy)
 /* Output the default trackDb visibility for all tracks
  * in trackDb if the track is not mentioned in the cart. */
 {
-database = cartString(cart, "db");
+database = cartUsualString(cart, "db", hDefaultDb());
 struct trackDb *tdb = NULL;
 // Some old sessions reference databases that are no longer present, and that triggers an errAbort
 // when calling hgTrackDb.  Just move on instead of errAborting.
 struct errCatch *errCatch = errCatchNew();
 if (errCatchStart(errCatch))
     tdb = hTrackDb(database);
 errCatchEnd(errCatch);
 if (errCatch->gotError)
     {
     fprintf(stderr, "outDefaultTracks: Error from hTrackDb: '%s'; Continuing...",
             errCatch->message->string);
     tdb = NULL;
     }
 errCatchFree(&errCatch);
 
@@ -997,42 +997,58 @@
 /* Prevent modification of the custom track collection just saved to namedSessionDb.  Under
  * copy-on-write hgCollection asks for its own trash copy before it writes, so this does
  * nothing.  refs #38273 */
 cartCopyLocalHubsOnSessionLoad(cart);
 return useCount;
 }
 
 char *doNewSession(char *userName)
 /* Save current settings in a new named session.
  * Return a message confirming what we did. */
 {
 if (userName == NULL)
     return "Unable to save session -- please log in and try again.";
 struct dyString *dyMessage = dyStringNew(2048);
 /* Clone: saveCartAsSession() removes this cart variable, which frees the cart's own copy. */
-char *sessionName = trimSpaces(cloneString(cartString(cart, hgsNewSessionName)));
+char *sessionName = trimSpaces(cloneString(cartUsualString(cart, hgsNewSessionName, "")));
 if (isEmpty(sessionName))
     return "Error: Unable to save a session without a name.  Please add one and try again.";
 
 char *encSessionName = cgiEncodeFull(sessionName);
-boolean shareSession = cartBoolean(cart, hgsNewSessionShare);
 char *encUserName = cgiEncodeFull(userName);
 struct sqlConnection *conn = hConnectCentral();
 
 if (sqlTableExists(conn, namedSessionTable))
     {
-    int useCount = saveCartAsSession(conn, encUserName, encSessionName, shareSession);
+    int sharingLevel;
+    /* Ask the request, not the cart: the cart keeps hgsNewSessionShare on purpose (see
+     * cleanHgSessionFromCart), so it is there long after the form that set it. */
+    if (cgiBooleanDefined(hgsNewSessionShare) || cgiVarExists(hgsNewSessionShare))
+        sharingLevel = cartBoolean(cart, hgsNewSessionShare);
+    else
+        {
+        /* The Save form always sends the checkbox's shadow, so this request did not come from it.
+         * Do not widen who can load the session: keep an existing session's level, and make a new
+         * one private (sqlQuickNum returns 0 when there is no row). */
+        struct dyString *query = sqlDyStringCreate(
+                 "select shared from %s where userName = '%s' and sessionName = '%s'",
+                 namedSessionTable, encUserName, encSessionName);
+        sharingLevel = sqlQuickNum(conn, query->string);
+        dyStringFree(&query);
+        }
+    boolean shareSession = (sharingLevel > 0);
+    int useCount = saveCartAsSession(conn, encUserName, encSessionName, sharingLevel);
     if (useCount > INITIAL_USE_COUNT)
 	dyStringPrintf(dyMessage,
 	  "Overwrote the contents of session <B>%s</B> "
 	  "(that %s be shared with other users).  "
 	  "%s %s",
 	  htmlEncode(sessionName), (shareSession ? "may" : "may not"),
 	  getSessionLink(encUserName, encSessionName),
 	  getSessionEmailLink(encUserName, encSessionName));
     else
 	dyStringPrintf(dyMessage,
 	  "Added a new session <B>%s</B> that %s be shared with other users.  "
 	  "%s %s",
 	  htmlEncode(sessionName), (shareSession ? "may" : "may not"),
 	  getSessionLink(encUserName, encSessionName),
 	  getSessionEmailLink(encUserName, encSessionName));
@@ -2072,31 +2088,31 @@
              encUserName, encSessionName, query);
 return atoi(sharedStr);
 }
 
 char *doReSaveSession(char *userName, char *actionVar)
 /* Load a session (which may have old trash and customTrash references) and re-save it
  * so that customTrash tables will be moved to customData* databases and trash paths
  * will be replaced with userdata (hg.conf sessionDataDir) paths.
  * NOTE: this is not intended to be reachable by the UI; it is for a script to update
  * old sessions to use the new sessionData locations. */
 {
 if (userName == NULL)
     return "Unable to re-save session -- please log in and try again.";
 struct sqlConnection *conn = hConnectCentral();
 /* Clone: cartLoadUserSession() and saveCartAsSession() both free the cart's own copy. */
-char *sessionName = trimSpaces(cloneString(cartString(cart, hgsNewSessionName)));
+char *sessionName = trimSpaces(cloneString(cartUsualString(cart, hgsNewSessionName, "")));
 if (isEmpty(sessionName))
     return "Error: Unable to save a session without a name.  Please add one and try again.";
 
 char *encUserName = cgiEncodeFull(userName);
 char *encSessionName = cgiEncodeFull(sessionName);
 int sharingLevel = getSharingLevel(conn, encUserName, encSessionName);
 cartLoadUserSession(conn, userName, sessionName, cart, NULL, actionVar);
 // No cartCopyLocalHubsOnSessionLoad because we're not going to make any track collection changes
 hubConnectLoadHubs(cart);
 // Some old sessions reference databases that are no longer present, and that triggers an errAbort
 // when cartHideDefaultTracks calls hgTrackDb.  Don't let that stop the process of updating other
 // stuff in the session.
 struct errCatch *errCatch = errCatchNew();
 if (errCatchStart(errCatch))
     cartHideDefaultTracks(cart);