fix(dashboard): bind editMessage to channel to stop repost loop
The retry refactor captured `const editMessage = channel.editMessage` and called it detached, losing `this`. Every status edit then threw "this.client is undefined", so each cycle failed all retries and reposted a fresh (notifying) status message — ~50 reposts in 30 minutes. Bind the method to the channel so the edit runs in place. Adds a regression test showing a detached method fails while a bound one succeeds. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -312,6 +312,41 @@ describe('editStatusMessageWithRetry', () => {
|
|||||||
expect(ok).toBe(false);
|
expect(ok).toBe(false);
|
||||||
expect(slept).toBe(0);
|
expect(slept).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('works with a channel method that relies on `this` when bound (regression)', async () => {
|
||||||
|
class FakeChannel {
|
||||||
|
client = { ok: true };
|
||||||
|
edits = 0;
|
||||||
|
async editMessage(): Promise<void> {
|
||||||
|
// Mirrors the real editMessage: throws if `this` is lost.
|
||||||
|
if (!this.client) throw new Error('this.client is undefined');
|
||||||
|
this.edits++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const channel = new FakeChannel();
|
||||||
|
|
||||||
|
// A detached method reference loses `this` → every attempt fails.
|
||||||
|
const detached = channel.editMessage;
|
||||||
|
const detachedOk = await editStatusMessageWithRetry({
|
||||||
|
editOnce: () => detached(),
|
||||||
|
maxRetries: 0,
|
||||||
|
retryDelayMs: 0,
|
||||||
|
sleep: async () => {},
|
||||||
|
});
|
||||||
|
expect(detachedOk).toBe(false);
|
||||||
|
expect(channel.edits).toBe(0);
|
||||||
|
|
||||||
|
// Binding to the channel (the fix) preserves `this` and succeeds.
|
||||||
|
const bound = channel.editMessage.bind(channel);
|
||||||
|
const boundOk = await editStatusMessageWithRetry({
|
||||||
|
editOnce: () => bound(),
|
||||||
|
maxRetries: 0,
|
||||||
|
retryDelayMs: 0,
|
||||||
|
sleep: async () => {},
|
||||||
|
});
|
||||||
|
expect(boundOk).toBe(true);
|
||||||
|
expect(channel.edits).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createCoalescingTrigger', () => {
|
describe('createCoalescingTrigger', () => {
|
||||||
|
|||||||
@@ -916,7 +916,10 @@ export async function startUnifiedDashboard(
|
|||||||
// immediately spawn a fresh status message. Retry the edit a couple of
|
// immediately spawn a fresh status message. Retry the edit a couple of
|
||||||
// times with a delay first; only give up (and repost) if all fail.
|
// times with a delay first; only give up (and repost) if all fail.
|
||||||
const editId = statusMessageId;
|
const editId = statusMessageId;
|
||||||
const editMessage = channel.editMessage;
|
// Bind to the channel: a detached method reference loses `this` and the
|
||||||
|
// edit throws ("this.client is undefined"), which would make every
|
||||||
|
// update fail and repost a fresh (notifying) message.
|
||||||
|
const editMessage = channel.editMessage.bind(channel);
|
||||||
// First render after start: 0 retries → repost immediately if the edit
|
// First render after start: 0 retries → repost immediately if the edit
|
||||||
// fails. Steady state: retry twice at 15s before reposting.
|
// fails. Steady state: retry twice at 15s before reposting.
|
||||||
const maxRetries = firstStatusRender ? 0 : STATUS_EDIT_MAX_RETRIES;
|
const maxRetries = firstStatusRender ? 0 : STATUS_EDIT_MAX_RETRIES;
|
||||||
|
|||||||
Reference in New Issue
Block a user