mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 11:15:00 +00:00
fix: clear delivery routing state when creating isolated cron sessions (#27778)
* fix: clear delivery routing state when creating isolated cron sessions When `resolveCronSession()` creates a new session (forceNew / isolated), the `...entry` spread preserves `lastThreadId`, `lastTo`, `lastChannel`, and `lastAccountId` from the prior session. This causes announce-mode cron deliveries to post as thread replies instead of channel top-level messages when `delivery.to` matches the channel of a prior conversation. Clear delivery routing metadata on new session creation so isolated cron sessions start with a clean delivery state. Closes #27751 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved) * fix: also clear deliveryContext to prevent lastThreadId repopulation normalizeSessionEntryDelivery (called on store writes) repopulates lastThreadId from deliveryContext.threadId. Clearing only the last* fields is insufficient — deliveryContext must also be cleared when creating a new isolated session. ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
This commit is contained in:
@@ -143,6 +143,94 @@ describe("resolveCronSession", () => {
|
|||||||
expect(result.sessionEntry.providerOverride).toBe("anthropic");
|
expect(result.sessionEntry.providerOverride).toBe("anthropic");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("clears delivery routing metadata and deliveryContext when forceNew is true", () => {
|
||||||
|
const result = resolveWithStoredEntry({
|
||||||
|
entry: {
|
||||||
|
sessionId: "existing-session-id-789",
|
||||||
|
updatedAt: NOW_MS - 1000,
|
||||||
|
systemSent: true,
|
||||||
|
lastChannel: "slack" as never,
|
||||||
|
lastTo: "channel:C0XXXXXXXXX",
|
||||||
|
lastAccountId: "acct-123",
|
||||||
|
lastThreadId: "1737500000.123456",
|
||||||
|
deliveryContext: {
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C0XXXXXXXXX",
|
||||||
|
threadId: "1737500000.123456",
|
||||||
|
},
|
||||||
|
modelOverride: "gpt-5.2",
|
||||||
|
},
|
||||||
|
fresh: true,
|
||||||
|
forceNew: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isNewSession).toBe(true);
|
||||||
|
// Delivery routing state must be cleared to prevent thread leaking.
|
||||||
|
// deliveryContext must also be cleared because normalizeSessionEntryDelivery
|
||||||
|
// repopulates lastThreadId from deliveryContext.threadId on store writes.
|
||||||
|
expect(result.sessionEntry.lastChannel).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.lastTo).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.lastAccountId).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.lastThreadId).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.deliveryContext).toBeUndefined();
|
||||||
|
// Per-session overrides must be preserved
|
||||||
|
expect(result.sessionEntry.modelOverride).toBe("gpt-5.2");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears delivery routing metadata when session is stale", () => {
|
||||||
|
const result = resolveWithStoredEntry({
|
||||||
|
entry: {
|
||||||
|
sessionId: "old-session-id",
|
||||||
|
updatedAt: NOW_MS - 86_400_000,
|
||||||
|
lastChannel: "slack" as never,
|
||||||
|
lastTo: "channel:C0XXXXXXXXX",
|
||||||
|
lastThreadId: "1737500000.999999",
|
||||||
|
deliveryContext: {
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C0XXXXXXXXX",
|
||||||
|
threadId: "1737500000.999999",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fresh: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isNewSession).toBe(true);
|
||||||
|
expect(result.sessionEntry.lastChannel).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.lastTo).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.lastAccountId).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.lastThreadId).toBeUndefined();
|
||||||
|
expect(result.sessionEntry.deliveryContext).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves delivery routing metadata when reusing fresh session", () => {
|
||||||
|
const result = resolveWithStoredEntry({
|
||||||
|
entry: {
|
||||||
|
sessionId: "existing-session-id-101",
|
||||||
|
updatedAt: NOW_MS - 1000,
|
||||||
|
systemSent: true,
|
||||||
|
lastChannel: "slack" as never,
|
||||||
|
lastTo: "channel:C0XXXXXXXXX",
|
||||||
|
lastThreadId: "1737500000.123456",
|
||||||
|
deliveryContext: {
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C0XXXXXXXXX",
|
||||||
|
threadId: "1737500000.123456",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fresh: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isNewSession).toBe(false);
|
||||||
|
expect(result.sessionEntry.lastChannel).toBe("slack");
|
||||||
|
expect(result.sessionEntry.lastTo).toBe("channel:C0XXXXXXXXX");
|
||||||
|
expect(result.sessionEntry.lastThreadId).toBe("1737500000.123456");
|
||||||
|
expect(result.sessionEntry.deliveryContext).toEqual({
|
||||||
|
channel: "slack",
|
||||||
|
to: "channel:C0XXXXXXXXX",
|
||||||
|
threadId: "1737500000.123456",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("creates new sessionId when entry exists but has no sessionId", () => {
|
it("creates new sessionId when entry exists but has no sessionId", () => {
|
||||||
const result = resolveWithStoredEntry({
|
const result = resolveWithStoredEntry({
|
||||||
entry: {
|
entry: {
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ export function resolveCronSession(params: {
|
|||||||
sessionId,
|
sessionId,
|
||||||
updatedAt: params.nowMs,
|
updatedAt: params.nowMs,
|
||||||
systemSent,
|
systemSent,
|
||||||
|
// When starting a fresh session (forceNew / isolated), clear delivery routing
|
||||||
|
// state inherited from prior sessions. Without this, lastThreadId leaks into
|
||||||
|
// the new session and causes announce-mode cron deliveries to post as thread
|
||||||
|
// replies instead of channel top-level messages.
|
||||||
|
// deliveryContext must also be cleared because normalizeSessionEntryDelivery
|
||||||
|
// repopulates lastThreadId from deliveryContext.threadId on store writes.
|
||||||
|
...(isNewSession && {
|
||||||
|
lastChannel: undefined,
|
||||||
|
lastTo: undefined,
|
||||||
|
lastAccountId: undefined,
|
||||||
|
lastThreadId: undefined,
|
||||||
|
deliveryContext: undefined,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
return { storePath, store, sessionEntry, systemSent, isNewSession };
|
return { storePath, store, sessionEntry, systemSent, isNewSession };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user