From 21ba37c48668d589437d9acf67429298a02adb8e Mon Sep 17 00:00:00 2001 From: bergeouss Date: Mon, 4 May 2026 16:51:53 +0000 Subject: [PATCH] fix: session list race condition (#1430) + read-only fs guard (#1470) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1430 — renderSessionList() had no staleness guard. Multiple concurrent callers (message send, rename, session switch) could race, allowing a slower older API response to overwrite _allSessions with stale data. Added a generation counter that increments on each call and discards responses from superseded generations. #1470 — docker_init.bash unconditionally called groupmod/usermod even on read-only root filesystems (podman with read_only=true). Added a writability check for /etc/group and /etc/passwd. If read-only and UID/GID already match, the mod is skipped gracefully. If they don't match, a clear error message suggests setting matching IDs or disabling read_only mode. --- docker_init.bash | 20 ++++++++++++++++++-- static/sessions.js | 9 +++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docker_init.bash b/docker_init.bash index 88f21456..750f31d2 100644 --- a/docker_init.bash +++ b/docker_init.bash @@ -188,8 +188,24 @@ if [ "A${whoami}" == "Ahermeswebuitoo" ]; then # We are altering the UID/GID of the hermeswebui user to the desired ones and restarting as that user # using usermod for the already create hermeswebui user, knowing it is not already in use # per usermod manual: "You must make certain that the named user is not executing any processes when this command is being executed" - sudo groupmod -o -g ${WANTED_GID} hermeswebui || error_exit "Failed to set GID of hermeswebui user" - sudo usermod -o -u ${WANTED_UID} hermeswebui || error_exit "Failed to set UID of hermeswebui user" + # Guard for read-only root filesystem (podman with read_only=true, issue #1470). + _readonly_root=false + if [ ! -w /etc/group ] || [ ! -w /etc/passwd ]; then + _readonly_root=true + echo " !! Detected read-only root filesystem — /etc/group or /etc/passwd is not writable" + fi + if [ "A${_readonly_root}" == "Atrue" ]; then + _current_hermeswebui_gid=$(id -g hermeswebui 2>/dev/null || echo "") + _current_hermeswebui_uid=$(id -u hermeswebui 2>/dev/null || echo "") + if [ "A${_current_hermeswebui_gid}" == "A${WANTED_GID}" ] && [ "A${_current_hermeswebui_uid}" == "A${WANTED_UID}" ]; then + echo " -- Skipping groupmod/usermod — hermeswebui already has UID ${WANTED_UID} GID ${WANTED_GID} and root fs is read-only" + else + error_exit "Cannot modify /etc/group or /etc/passwd (read-only root fs). Set UID=${_current_hermeswebui_uid} and GID=${_current_hermeswebui_gid} to match, or run without read_only=true. See issue #1470." + fi + else + sudo groupmod -o -g ${WANTED_GID} hermeswebui || error_exit "Failed to set GID of hermeswebui user" + sudo usermod -o -u ${WANTED_UID} hermeswebui || error_exit "Failed to set UID of hermeswebui user" + fi sudo chown -R ${WANTED_UID}:${WANTED_GID} /home/hermeswebui || error_exit "Failed to set owner of /home/hermeswebui" save_env /tmp/hermeswebuitoo_env.txt # restart the script as hermeswebui set with the correct UID/GID this time diff --git a/static/sessions.js b/static/sessions.js index 111e0b89..2f307bb9 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -1359,7 +1359,14 @@ window.addEventListener('resize',()=>{ if(_sessionActionMenu && _sessionActionAnchor) _positionSessionActionMenu(_sessionActionAnchor); }); +// Generation counter to discard stale API responses (issue #1430). +// Multiple callers (message send, rename, session switch) fire renderSessionList() +// concurrently. Without this guard, a slower older response can overwrite _allSessions +// with stale data, causing sessions to vanish from the sidebar. +let _renderSessionListGen = 0; + async function renderSessionList(){ + const _gen = ++_renderSessionListGen; try{ if(!($('sessionSearch').value||'').trim()) _contentSearchResults = []; const allProfilesQS = _showAllProfiles ? '?all_profiles=1' : ''; @@ -1367,6 +1374,8 @@ async function renderSessionList(){ api('/api/sessions' + allProfilesQS), api('/api/projects' + allProfilesQS), ]); + // Discard stale response — a newer renderSessionList() call superseded us. + if (_gen !== _renderSessionListGen) return; // Server's other_profile_count tells us how many sessions exist outside the // active profile so the "Show N from other profiles" toggle can render // without a second round-trip. Stashed on the module for renderSessionListFromCache.