fix(mattermost): wait for in-flight preview send in onSettled cleanup

When the first preview POST is still in flight (patchSending=true, streamMessageId=null),
the previous onSettled check was skipped entirely — the POST would resolve after cleanup
and leave an orphaned preview post with no interval to clear it.

Fix: trigger cleanup when either streamMessageId is set OR patchSending is true.
Stop the interval immediately, clear pending state, then wait up to 3s for patchSending
to clear before capturing the final streamMessageId and deleting the post.
This commit is contained in:
teconomix 2026-03-20 07:15:22 +00:00
parent e2c4d8bd45
commit 0ecdda1433

View File

@ -1704,16 +1704,31 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
// the reply pipeline produces no final payload (e.g. messaging-tool sends that
// are suppressed, or empty/heartbeat responses). Without this, onPartialReply
// can create a Mattermost post that is never deleted or patched with final text.
if (streamMessageId && blockStreamingClient) {
//
// We must also handle the race where the first preview POST is still in flight
// (patchSending=true, streamMessageId=null): stopPatchInterval() prevents new
// ticks, and the async cleanup waits for patchSending to clear so it can capture
// the messageId that the in-flight send will set.
if ((streamMessageId || patchSending) && blockStreamingClient) {
stopPatchInterval();
const orphanId = streamMessageId;
streamMessageId = null;
pendingPatchText = "";
lastSentText = "";
patchSending = false;
void deleteMattermostPost(blockStreamingClient, orphanId).catch(() => {
// Best-effort — the run is already complete.
});
const client = blockStreamingClient;
void (async () => {
// Wait for any in-flight send/patch to settle so we get the final messageId.
const deadline = Date.now() + 3000;
while (patchSending && Date.now() < deadline) {
await new Promise((r) => setTimeout(r, 50));
}
patchSending = false;
const orphanId = streamMessageId;
streamMessageId = null;
if (orphanId) {
await deleteMattermostPost(client, orphanId).catch(() => {
// Best-effort — the run is already complete.
});
}
})();
}
},
run: () =>