From dfef70b61b98382a1f78fc6d970fb24ac86c4e36 Mon Sep 17 00:00:00 2001 From: Agando Date: Fri, 23 Oct 2026 20:30:20 +0200 Subject: fix(chat): three concurrency holes found reviewing my own patch (v4.6.77) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. chat.py renamed the temp file AFTER releasing the flock, so two writers could each read the pre-replace snapshot and the later rename silently dropped the earlier message. os.replace now happens inside the lock. 2. The agent cursor was global. After hermes-reset it pointed past the end of the new empty thread, so every read returned nothing — the agent would think it had been ignored. Key is now per thread and reset clears every chat cursor. 3. chat-relay.sh had no overlap guard: a slow agent run (up to 5 min) could collide with the next tick and answer the same thread twice. flock -n on /tmp/chat-relay.lock. Verified: stale cursor 'webx11_chat_agent_cursor_oldthread00000' set to 9999999999999, hermes-reset -> zero cursor keys remain, read returns {messages:[], note:'no new messages...'}. --- modules/core/module-aiassist.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'modules/core/module-aiassist.js') diff --git a/modules/core/module-aiassist.js b/modules/core/module-aiassist.js index cefff5d..c3ebe1f 100644 --- a/modules/core/module-aiassist.js +++ b/modules/core/module-aiassist.js @@ -2711,8 +2711,9 @@ buildSystemPrompt() { // runs every 30 s and advances the shared cursor, so a read that used it // returned nothing — the answer was printed for the human and the model // never saw it. Two readers, two cursors. First read is from 0. + const acKey = 'webx11_chat_agent_cursor_' + (st.thread || 'new'); let ac = 0; - try { ac = parseInt(localStorage.getItem('webx11_chat_agent_cursor') || '0', 10) || 0; } catch (e) { ac = 0; } + try { ac = parseInt(localStorage.getItem(acKey) || '0', 10) || 0; } catch (e) { ac = 0; } const resp = await fetch( '/spaces/newos4/api/chat/poll.php' + (ac ? '?since=' + ac : ''), { headers: { 'X-Chat-Token': st.token } }); @@ -2720,7 +2721,7 @@ buildSystemPrompt() { if (!resp.ok || !j || !j.success) { return JSON.stringify({ error: (j && j.error) || ('HTTP ' + resp.status) }); } - if (j.cursor) { try { localStorage.setItem('webx11_chat_agent_cursor', String(j.cursor)); } catch (e) {} } + if (j.cursor) { try { localStorage.setItem(acKey, String(j.cursor)); } catch (e) {} } const msgs = (j.messages || []).slice(-10).map(m => ({ author: m.author, ts: m.ts, text: m.text })); -- cgit v1.2.3