Found during the PR #26 post-merge review (pre-existing on both main and integration/sim, not introduced by the merge).
Bug
In generateRemoteResponseImpl (src/services/generationServiceHelpers.ts), the onError callback always runs resetAfterGenerationError(svc); throw error;. In openAICompatibleProvider.generate() (src/services/providers/openAICompatibleProvider.ts), the callbacks.onError(new Error('No model selected')); return; guard sits outside the function's own try. Because onError now always throws, the throw escapes generate() and rejects its promise, so the outer catch in generateRemoteResponseImpl fires too and calls resetAfterGenerationError(svc, { markServerOffline: true }) a second time for the same failure.
Trigger
Select a remote server whose activeRemoteTextModelId/config.modelId is empty (model unselected/removed) and send a message. onError fires (reset #1, no offline flag), throws, generate() rejects, outer catch runs reset #2 (with markServerOffline: true). Between reset #1's resetState() (which flips the global isGenerating off and fires listeners) and reset #2, a listener-driven queue drain could start a new generation whose clearStreamingMessage()/resetState() gets clobbered by the stale second reset.
Fix direction
resetAfterGenerationError is designed to run once per failure. Either track a handled flag on svc so onError handlers don't rethrow when the error already came from onError, or make providers' callbacks.onError(...); return; guards not let the rethrow reach the caller's await.
Found during the PR #26 post-merge review (pre-existing on both
mainandintegration/sim, not introduced by the merge).Bug
In
generateRemoteResponseImpl(src/services/generationServiceHelpers.ts), theonErrorcallback always runsresetAfterGenerationError(svc); throw error;. InopenAICompatibleProvider.generate()(src/services/providers/openAICompatibleProvider.ts), thecallbacks.onError(new Error('No model selected')); return;guard sits outside the function's owntry. BecauseonErrornow always throws, the throw escapesgenerate()and rejects its promise, so the outercatchingenerateRemoteResponseImplfires too and callsresetAfterGenerationError(svc, { markServerOffline: true })a second time for the same failure.Trigger
Select a remote server whose
activeRemoteTextModelId/config.modelIdis empty (model unselected/removed) and send a message.onErrorfires (reset #1, no offline flag), throws,generate()rejects, outer catch runs reset #2 (withmarkServerOffline: true). Between reset #1'sresetState()(which flips the globalisGeneratingoff and fires listeners) and reset #2, a listener-driven queue drain could start a new generation whoseclearStreamingMessage()/resetState()gets clobbered by the stale second reset.Fix direction
resetAfterGenerationErroris designed to run once per failure. Either track ahandledflag onsvcsoonErrorhandlers don't rethrow when the error already came fromonError, or make providers'callbacks.onError(...); return;guards not let the rethrow reach the caller'sawait.