fix(turns): clear progress ticker on abnormal turn exit to prevent zombie progress messages

turnController.finish() is the only path that clears the 5s progress ticker,
but it lives inside the caller's try block in message-runtime-turns.ts. When
runAgent throws / is aborted / is killed (e.g. a user "중단"), control jumps to
finally and finish() is skipped, leaving the ticker editing the Discord message
forever — an orphaned "stuck at 0s" zombie progress message with no backing
task/attempt.

Add an idempotent MessageTurnController.dispose() that tears down the progress
ticker + idle timer, and always call it from the caller's finally so timers are
cleared on every exit path. Adds a regression test.
This commit is contained in:
Codex
2026-07-26 18:07:17 +09:00
parent 58811b2700
commit 142929ba39
3 changed files with 81 additions and 0 deletions

View File

@@ -479,6 +479,26 @@ export class MessageTurnController {
this.progressTicker = null;
}
/**
* Idempotent teardown of background timers (progress ticker + idle timer).
*
* `finish()` already tears these down on the normal completion path, but it
* lives inside the caller's `try` block. When the agent run throws / is
* aborted / is killed, control jumps straight to the caller's `finally` and
* `finish()` is skipped — leaving the 5s progress ticker editing the Discord
* message forever (an orphaned "stuck at 0s" zombie progress message).
*
* Callers MUST invoke this from a `finally` so timers are cleared on every
* exit path. Safe to call more than once and safe to call after finish().
*/
dispose(): void {
this.clearProgressTicker();
if (this.idleTimer) {
clearTimeout(this.idleTimer);
this.idleTimer = null;
}
}
private resetProgressState(): void {
this.clearProgressTicker();
this.pendingProgressText = null;