fix(imessage): cap per-chat group-allowlist warn-once cache (#102658)

* fix(imessage): cap per-chat group-allowlist warn-once cache

Replace unbounded perChatWarned Set with createDedupeCache(maxSize=512)
to keep long-running iMessage monitor memory stable. The cache grows
with every distinct group chat the gateway sees; without a cap it can
accumulate entries indefinitely.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(imessage): prove warning cache eviction

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Wynne668 2026-07-09 23:16:03 +08:00 committed by GitHub
parent 14558dee87
commit baa009e26f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 4 deletions

View file

@ -143,4 +143,24 @@ describe("warnGroupAllowlistDropPerChatOnce", () => {
);
expect(messages).toHaveLength(0);
});
it("bounds warn-once state by least-recently-used account/chat pairs", () => {
const messages: string[] = [];
const log = (message: string) => messages.push(message);
const warn = (chatId: number) =>
warnGroupAllowlistDropPerChatOnce({ accountId: "default", chatId, log });
for (let chatId = 0; chatId < 512; chatId += 1) {
expect(warn(chatId)).toBe(true);
}
expect(warn(0)).toBe(false);
expect(warn(512)).toBe(true);
messages.length = 0;
expect(warn(0)).toBe(false);
expect(warn(1)).toBe(true);
expect(warn(512)).toBe(false);
expect(messages).toHaveLength(1);
expect(messages[0]).toContain("chat_id=1");
});
});

View file

@ -7,8 +7,16 @@
// during iMessage config migration. See
// https://github.com/openclaw/openclaw/issues/78749.
import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
const PER_CHAT_WARNING_CACHE_MAX_SIZE = 512;
const startupWarned = new Set<string>();
const perChatWarned = new Set<string>();
// Retain warn-once state for recently active chats without allowing every chat
// seen over the process lifetime to accumulate indefinitely.
const perChatWarned = createDedupeCache({
maxSize: PER_CHAT_WARNING_CACHE_MAX_SIZE,
ttlMs: 0,
});
/**
* Fires once per `accountId` at monitor startup when `groupPolicy === "allowlist"`
@ -50,7 +58,7 @@ export function warnGroupAllowlistMisconfigOnce(params: {
/**
* Fires once per `accountId:chat_id` when the runtime allowlist gate drops a
* group message because that chat_id is not in `channels.imessage.groups`.
* Bounded by the number of distinct group chats the gateway sees.
* Retains up to 512 recently active account/chat pairs; evicted chats may warn again.
*/
export function warnGroupAllowlistDropPerChatOnce(params: {
accountId: string;
@ -62,10 +70,9 @@ export function warnGroupAllowlistDropPerChatOnce(params: {
return false;
}
const key = `imessage:${params.accountId}:${chat}`;
if (perChatWarned.has(key)) {
if (perChatWarned.check(key)) {
return false;
}
perChatWarned.add(key);
params.log(
`imessage: dropping group message from chat_id=${chat} (account "${params.accountId}") — ` +
`not in channels.imessage.groups allowlist. ` +