What happened
WsBridgeBase ignores close events from a detached socket, but accepts its open and message events. After stop() completes, a delayed open event sets running back to true, and a delayed message still reaches protocol handling.
On the shared Discord/QQ gateway layer, late READY and HELLO frames can also overwrite session/readiness state or restart heartbeat work. After the same bridge starts a replacement socket, the previous socket can still modify the new connection state. The transport base is shared with DingTalk Stream.
Expected: open/message callbacks only act on the current socket while the bridge has not been explicitly stopped. Current-socket events must continue working.
How to reproduce
Build the repository, save the following as ws-retirement-repro.mjs in its root, and run node ws-retirement-repro.mjs. The fake socket makes delayed events deterministic and uses no network or provider credentials.
import { createDefaultBotChannel } from '@maka/core/settings';
import { WsBridgeBase } from './packages/runtime/dist/bots/ws-bridge-base.js';
class FakeSocket extends EventTarget {
close() {}
fire(type, data) {
const event = new Event(type);
if (data !== undefined) Object.defineProperty(event, 'data', { value: data });
this.dispatchEvent(event);
}
}
class FixtureBridge extends WsBridgeBase {
sockets = [];
messages = [];
createWebSocket() {
const socket = new FakeSocket();
this.sockets.push(socket);
return socket;
}
async openConnection() { this.connect('wss://fixture.invalid'); }
checkCredentials() { return null; }
decideClose() { return { kind: 'stopped' }; }
handleWsMessage(data) { this.messages.push(data); }
}
const bridge = new FixtureBridge('discord', {
...createDefaultBotChannel('discord'), enabled: true, token: 'fixture-token',
});
await bridge.start();
const retired = bridge.sockets[0];
await bridge.stop();
const runningImmediatelyAfterStop = bridge.isRunning();
retired.fire('open');
retired.fire('message', 'late payload');
console.log(JSON.stringify({
runningImmediatelyAfterStop,
runningAfterLateOpen: bridge.isRunning(),
lateMessagesProcessed: bridge.messages.length,
}));
Actual output on the affected upstream revision:
{"runningImmediatelyAfterStop":false,"runningAfterLateOpen":true,"lateMessagesProcessed":1}
Expected:
{"runningImmediatelyAfterStop":false,"runningAfterLateOpen":false,"lateMessagesProcessed":0}
Environment
- Maka commit: ab5996b (upstream main)
- OS: Windows, x64
- Surface: shared outbound WebSocket Bot transport in packages/runtime
- Node.js: v24.20.0
- Fake-socket reproduction; no live provider account or SDK end-to-end test.
Logs, screenshots, or additional context
The close callback already checks this.ws !== ws; open and message lack the corresponding lifecycle fence. I am preparing a two-line guard fix and three deterministic regressions for post-stop open, post-stop messages/heartbeats, and old-vs-current sockets after restart.
Related: #5945 / #5946 address retired bridge subscriptions in BotRegistry. This issue concerns a retired socket inside a bridge, including a bridge that remains current during reconnect/restart, so it is an independent transport-layer fix.
Automated report prepared with OpenAI Codex on behalf of the contributor.
What happened
WsBridgeBase ignores close events from a detached socket, but accepts its open and message events. After stop() completes, a delayed open event sets running back to true, and a delayed message still reaches protocol handling.
On the shared Discord/QQ gateway layer, late READY and HELLO frames can also overwrite session/readiness state or restart heartbeat work. After the same bridge starts a replacement socket, the previous socket can still modify the new connection state. The transport base is shared with DingTalk Stream.
Expected: open/message callbacks only act on the current socket while the bridge has not been explicitly stopped. Current-socket events must continue working.
How to reproduce
Build the repository, save the following as
ws-retirement-repro.mjsin its root, and runnode ws-retirement-repro.mjs. The fake socket makes delayed events deterministic and uses no network or provider credentials.Actual output on the affected upstream revision:
{"runningImmediatelyAfterStop":false,"runningAfterLateOpen":true,"lateMessagesProcessed":1}Expected:
{"runningImmediatelyAfterStop":false,"runningAfterLateOpen":false,"lateMessagesProcessed":0}Environment
Logs, screenshots, or additional context
The close callback already checks this.ws !== ws; open and message lack the corresponding lifecycle fence. I am preparing a two-line guard fix and three deterministic regressions for post-stop open, post-stop messages/heartbeats, and old-vs-current sockets after restart.
Related: #5945 / #5946 address retired bridge subscriptions in BotRegistry. This issue concerns a retired socket inside a bridge, including a bridge that remains current during reconnect/restart, so it is an independent transport-layer fix.
Automated report prepared with OpenAI Codex on behalf of the contributor.