From 94f703a8459fb71e23ab66a79f95be311323753b Mon Sep 17 00:00:00 2001 From: Eliot Date: Thu, 23 Apr 2026 10:05:05 +0200 Subject: [PATCH] fix(sessions): updateLastRoute must not bump updatedAt (#49515) (#49588) updateLastRoute() used mergeSessionEntry which bumps updatedAt to Date.now() on every inbound message. This prevented session idle and daily reset from ever firing, since evaluateSessionFreshness() always saw a fresh updatedAt. The fix from #32379 patched recordSessionMetaFromInbound to use mergeSessionEntryPreserveActivity, but missed updateLastRoute() in the same inbound pipeline. Changes: - Remove explicit updatedAt from updateLastRoute basePatch - Switch from mergeSessionEntry to mergeSessionEntryPreserveActivity - Add regression test verifying updatedAt is preserved - Update existing test assertion to match corrected behavior Fixes #49515 --- src/config/sessions.test.ts | 33 ++++++++++++++++++++++++++++++++- src/config/sessions/store.ts | 5 +++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/config/sessions.test.ts b/src/config/sessions.test.ts index 2e3098cc739..965f1d20bdf 100644 --- a/src/config/sessions.test.ts +++ b/src/config/sessions.test.ts @@ -243,7 +243,8 @@ describe("sessions", () => { const store = loadSessionStore(storePath); expect(store[mainSessionKey]?.sessionId).toBe("sess-1"); - expect(store[mainSessionKey]?.updatedAt).toBeGreaterThanOrEqual(123); + // updateLastRoute must preserve existing updatedAt (activity timestamp) + expect(store[mainSessionKey]?.updatedAt).toBe(123); expect(store[mainSessionKey]?.lastChannel).toBe("telegram"); expect(store[mainSessionKey]?.lastTo).toBe("12345"); expect(store[mainSessionKey]?.deliveryContext).toEqual({ @@ -355,6 +356,36 @@ describe("sessions", () => { expect(store[sessionKey]?.origin?.chatType).toBe("group"); }); + it("updateLastRoute does not bump updatedAt on existing sessions (#49515)", async () => { + const mainSessionKey = "agent:main:main"; + const frozenUpdatedAt = 1000; + const { storePath } = await createSessionStoreFixture({ + prefix: "updateLastRoute-preserve-activity", + entries: { + [mainSessionKey]: buildMainSessionEntry({ + updatedAt: frozenUpdatedAt, + }), + }, + }); + + await updateLastRoute({ + storePath, + sessionKey: mainSessionKey, + deliveryContext: { + channel: "telegram", + to: "99999", + }, + }); + + const store = loadSessionStore(storePath); + // Route updates must not refresh activity timestamps; idle/daily reset + // evaluation relies on updatedAt from actual session turns. + expect(store[mainSessionKey]?.updatedAt).toBe(frozenUpdatedAt); + // Routing fields should still be updated + expect(store[mainSessionKey]?.lastChannel).toBe("telegram"); + expect(store[mainSessionKey]?.lastTo).toBe("99999"); + }); + it("updateSessionStoreEntry preserves existing fields when patching", async () => { const sessionKey = "agent:main:main"; const { storePath } = await createSessionStoreFixture({ diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index 46feeda0695..00c6158eeb2 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -772,14 +772,15 @@ export async function updateLastRoute(params: { }) : null; const basePatch: Partial = { - updatedAt: Math.max(existing?.updatedAt ?? 0, now), deliveryContext: normalized.deliveryContext, lastChannel: normalized.lastChannel, lastTo: normalized.lastTo, lastAccountId: normalized.lastAccountId, lastThreadId: normalized.lastThreadId, }; - const next = mergeSessionEntry( + // Route updates must not refresh activity timestamps; idle/daily reset + // evaluation relies on updatedAt from actual session turns (#49515). + const next = mergeSessionEntryPreserveActivity( existing, metaPatch ? { ...basePatch, ...metaPatch } : basePatch, );