{"record":{"id":"e79ec63aee18e7a2","repo":"router-for-me/CLIProxyAPI","slug":"s-e79ec6","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/stream_bridge.go","lineNumber":101,"sourceCode":"\t\tvar emitC <-chan streamBridgeEmit\n\t\tif len(queue) < streamBridgeBufferSize {\n\t\t\temitC = s.emits\n\t\t}\n\t\tvar outputC chan pluginapi.ExecutorStreamChunk\n\t\tvar next pluginapi.ExecutorStreamChunk\n\t\tif len(queue) > 0 {\n\t\t\toutputC = s.chunks\n\t\t\tnext = queue[0]\n\t\t}\n\n\t\tselect {\n\t\tcase <-s.abort:\n\t\t\treturn\n\t\tcase request := <-s.closes:\n\t\t\ts.markClosed()\n\t\t\tclose(request.accepted)\n\t\t\tif request.errorMessage != \"\" {\n\t\t\t\tqueue = append(queue, pluginapi.ExecutorStreamChunk{Err: fmt.Errorf(\"%s\", request.errorMessage)})\n\t\t\t}\n\t\t\tfor len(queue) > 0 {\n\t\t\t\tselect {\n\t\t\t\tcase <-s.abort:\n\t\t\t\t\treturn\n\t\t\t\tcase s.chunks <- queue[0]:\n\t\t\t\t\tqueue = queue[1:]\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\tcase request := <-emitC:\n\t\t\tif err := request.ctx.Err(); err != nil {\n\t\t\t\trequest.done <- err\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tqueue = append(queue, request.chunk)\n\t\t\trequest.done <- nil\n\t\tcase outputC <- next:","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/stream_bridge.go#L83-L119","documentation":"The stream bridge in internal/pluginhost/stream_bridge.go received a close request whose errorMessage was non-empty and converts it into a terminal stream chunk (Err: fmt.Errorf(\"%s\", errorMessage)) that is flushed to the SSE consumer before the stream ends. It is the mechanism that surfaces a plugin-side streaming failure (mid-generation error, aborted request) as an error event in the OpenAI/Gemini/Claude-compatible stream.","triggerScenarios":"A streaming executor call to a plugin fails partway: the host closes the bridge with an errorMessage, and the queued chunk carrying that error is delivered on the chunks channel before return.","commonSituations":"Upstream provider error during generation (rate limit, content filter, disconnect); plugin panic during streaming; client disconnect racing the close path (queued chunks drained until abort).","solutions":["Inspect the error text in the stream event and the plugin/provider logs for the root cause","Retry the request if the underlying error was transient (429/5xx from the provider)","If you own the plugin, ensure stream errors carry a descriptive message so this chunk is diagnosable"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"for chunk := range stream {\n    if chunk.Err != nil {\n        if isTransient(chunk.Err) { // 429/5xx text from provider\n            return retryWithBackoff(ctx, req)\n        }\n        return chunk.Err // terminal stream error from plugin close\n    }\n    emit(chunk)\n}","preventionTips":["Treat any chunk carrying Err as terminal for that stream; do not continue consuming after it","Retry only transient provider errors; propagate the message text to the client's error event"],"tags":["streaming","plugin","sse","error-passthrough"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}