[object Object]

← back to Ticket System

chore: harden DM thread-walk against cyclic re-chains (cycle guard in threadRootMid + server rootOf); session-close review pass

fda28cb2ca139275c13e60d15d809c25c031c6ce · 2026-07-26 20:53:02 -0700 · Steve Abrams

Files touched

Diff

commit fda28cb2ca139275c13e60d15d809c25c031c6ce
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Jul 26 20:53:02 2026 -0700

    chore: harden DM thread-walk against cyclic re-chains (cycle guard in threadRootMid + server rootOf); session-close review pass
---
 lib.js    | 5 +++--
 server.js | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/lib.js b/lib.js
index da46fe3..d6ce20e 100644
--- a/lib.js
+++ b/lib.js
@@ -75,8 +75,9 @@ function messages() {
   return map;
 }
 
-// Walk re-links up to the conversation root mid.
-function threadRootMid(mid, mm) { let id = mid, c = mm.get(mid); while (c && c.re && mm.get(c.re)) { id = c.re; c = mm.get(id); } return id; }
+// Walk re-links up to the conversation root mid. Visited set prevents infinite
+// loop if a corrupt/cyclical re-chain ever appears in the event log.
+function threadRootMid(mid, mm) { let id = mid, c = mm.get(mid); const seen = new Set([id]); while (c && c.re && mm.get(c.re)) { if (seen.has(c.re)) break; seen.add(c.re); id = c.re; c = mm.get(id); } return id; }
 
 // Every agent that has sent or been addressed in a message's conversation.
 function threadParticipants(mid, mm) {
diff --git a/server.js b/server.js
index fa9705a..2e1083f 100644
--- a/server.js
+++ b/server.js
@@ -22,7 +22,7 @@ function page() {
       ${!t.comments.length && !t.actions.length ? '<div class="c none">no comments yet</div>' : ''}</div></div>`;
   // ── Direct-message conversations, grouped into threads ──
   const mm = messages();
-  const rootOf = mid => { let id = mid, c = mm.get(mid); while (c && c.re && mm.get(c.re)) { id = c.re; c = mm.get(id); } return id; };
+  const rootOf = mid => { let id = mid, c = mm.get(mid); const seen = new Set([id]); while (c && c.re && mm.get(c.re)) { if (seen.has(c.re)) break; seen.add(c.re); id = c.re; c = mm.get(id); } return id; };
   const threads = new Map();
   for (const m of mm.values()) { const r = rootOf(m.mid); (threads.get(r) || threads.set(r, []).get(r)).push(m); }
   const convos = [...threads.values()].map(ms => ms.sort((a, b) => a.ts < b.ts ? -1 : 1))

← fb0737f harden @mention routing: only DM known agents (or broadcast)  ·  back to Ticket System  ·  auto-save: 2026-07-26T21:15:59 (2 files) — server.js office. f49cf45 →