From 7af9e52e02613a9af582f300ec79509fc6c4d7d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 18:06:38 +0000 Subject: [PATCH] fix: stop orphaned heartbeat manager and fix resume IDENTIFY bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1 (resume, line 2840): ResetHeartbeatCount → StopHeartbeats. The old heartbeat manager was kept alive with a reset count instead of being terminated. It accumulated unacknowledged heartbeats and eventually fired a HeartbeatInequalityDisconnect, tearing down the newly-resumed connection. Bug 2 (on_hello_event, lines 3384-3432): on_hello_event now checks whether we are doing a fresh connection (GettingReady/Welcomed) or a resume (Identified). During a resume the function previously: - transitioned unconditionally to Welcomed state, losing session_id, resume_gateway_url and sequence - called send_identify(), sending IDENTIFY to Discord and overriding the RESUME that was already queued Now, when the incoming state is Identified (resume path), the function stops the old heartbeat manager defensively, creates a new one, and stays in Identified state — preserving all session fields. IDENTIFY is not sent; the RESUME queued by resume() is used instead. As a result RESUMED is handled correctly by the existing Identified on_dispatch handler and all events flow normally after a resume. Agent-Logs-Url: https://github.com/folospior/grom/sessions/fd438628-6f79-495c-bc1b-209a482845b4 Co-authored-by: folospior <111700958+folospior@users.noreply.github.com> --- src/grom/gateway.gleam | 50 ++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/grom/gateway.gleam b/src/grom/gateway.gleam index dc3d3f5..585dce2 100644 --- a/src/grom/gateway.gleam +++ b/src/grom/gateway.gleam @@ -2837,7 +2837,7 @@ fn resume(connection_state: Connection, resuming_info: ResumingInfo) -> Nil { connection_state.manager, SendUserMessage(StartSendResume(resuming_info)), ) - process.send(resuming_info.heartbeat_manager, ResetHeartbeatCount) + process.send(resuming_info.heartbeat_manager, StopHeartbeats) } fn reconnect(connection_state: Connection) -> Nil { @@ -3385,6 +3385,15 @@ fn on_hello_event( connection_state: Connection, event: HelloMessage, ) -> stratus.Next(Connection, a) { + // Stop any existing heartbeat manager before creating a new one. + // This is necessary when reconnecting or resuming, where a heartbeat manager + // from the previous connection may still be running. + case connection_state { + Welcomed(heartbeat_manager:, ..) | Identified(heartbeat_manager:, ..) -> + process.send(heartbeat_manager, StopHeartbeats) + GettingReady(..) -> Nil + } + let heartbeat_start_result = start_heartbeats( every: event.heartbeat_interval, @@ -3392,19 +3401,32 @@ fn on_hello_event( ) case heartbeat_start_result { - Ok(heartbeat_manager) -> { - let state = - Welcomed( - gateway_url: connection_state.gateway_url, - identify: connection_state.identify, - subject: connection_state.subject, - manager: connection_state.manager, - heartbeat_manager: heartbeat_manager.data, - sequence: None, - ) - send_identify(state) - stratus.continue(state) - } + Ok(heartbeat_manager) -> + case connection_state { + GettingReady(..) | Welcomed(..) -> { + // Fresh connection: transition to Welcomed and send IDENTIFY. + let state = + Welcomed( + gateway_url: connection_state.gateway_url, + identify: connection_state.identify, + subject: connection_state.subject, + manager: connection_state.manager, + heartbeat_manager: heartbeat_manager.data, + sequence: None, + ) + send_identify(state) + stratus.continue(state) + } + Identified(..) -> { + // Resume: keep the Identified state (preserving session_id, + // resume_gateway_url, and sequence) and only replace the + // heartbeat manager. The RESUME payload was already queued + // in resume(), so we must not send IDENTIFY here. + stratus.continue( + Identified(..connection_state, heartbeat_manager: heartbeat_manager.data), + ) + } + } Error(err) -> send_error(err, connection_state) } } -- 2.51.2