fix: prevent typing indicator leak on agent run exceptions and follow-up race
Two leak paths caused Discord typing indicators to persist indefinitely: 1. If runAgent() threw before turnController.finish(), setTyping(false) was never called. Wrap the agent run in try/finally to guarantee cleanup. 2. Follow-up message piping called setTyping(true) fire-and-forget. If the Promise resolved after finish()'s setTyping(false), the typing interval was recreated with nobody to clear it. Await the call to serialize the on/off sequence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -345,33 +345,40 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
||||
|
||||
await turnController.start();
|
||||
|
||||
const output = await runAgent(group, prompt, chatJid, runId, (result) =>
|
||||
turnController.handleOutput(result),
|
||||
);
|
||||
|
||||
const { deliverySucceeded, visiblePhase } =
|
||||
await turnController.finish(output);
|
||||
|
||||
if (!deliverySucceeded) {
|
||||
logger.warn(
|
||||
{ chatJid, group: group.name, groupFolder: group.folder, runId },
|
||||
'Persisted produced output for delivery retry without rerunning agent',
|
||||
try {
|
||||
const output = await runAgent(group, prompt, chatJid, runId, (result) =>
|
||||
turnController.handleOutput(result),
|
||||
);
|
||||
return false;
|
||||
|
||||
const { deliverySucceeded, visiblePhase } =
|
||||
await turnController.finish(output);
|
||||
|
||||
if (!deliverySucceeded) {
|
||||
logger.warn(
|
||||
{ chatJid, group: group.name, groupFolder: group.folder, runId },
|
||||
'Persisted produced output for delivery retry without rerunning agent',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
chatJid,
|
||||
group: group.name,
|
||||
groupFolder: group.folder,
|
||||
runId,
|
||||
visiblePhase,
|
||||
},
|
||||
'Queued run completed successfully',
|
||||
);
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
// Safety net: always clear typing even if runAgent() or finish() throws.
|
||||
// Prevents stuck typing indicators when exceptions bypass the normal
|
||||
// turnController.finish() → setTyping(false) path.
|
||||
await channel.setTyping?.(chatJid, false);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
chatJid,
|
||||
group: group.name,
|
||||
groupFolder: group.folder,
|
||||
runId,
|
||||
visiblePhase,
|
||||
},
|
||||
'Queued run completed successfully',
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -523,7 +530,7 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
||||
endSeq,
|
||||
);
|
||||
}
|
||||
channel
|
||||
await channel
|
||||
.setTyping?.(chatJid, true)
|
||||
?.catch((err) =>
|
||||
logger.warn(
|
||||
|
||||
Reference in New Issue
Block a user