{"record":{"id":"95444f657aabb31b","repo":"cloudflare/cloudflared","slug":"timeout-waiting-for-second-stream-to-finish","errorCode":null,"errorMessage":"timeout waiting for second stream to finish","messagePattern":"timeout waiting for second stream to finish","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"stream/stream.go","lineNumber":74,"sourceCode":"\t}\n}\n\nfunc (s *bidirectionalStreamStatus) markUniStreamDone() {\n\tatomic.StoreUint32(&s.anyDone, 1)\n\ts.doneChan <- struct{}{}\n}\n\nfunc (s *bidirectionalStreamStatus) wait(maxWaitForSecondStream time.Duration) error {\n\t<-s.doneChan\n\n\t// Only wait for second stream to finish if maxWait is greater than zero\n\tif maxWaitForSecondStream > 0 {\n\t\ttimer := time.NewTimer(maxWaitForSecondStream)\n\t\tdefer timer.Stop()\n\n\t\tselect {\n\t\tcase <-timer.C:\n\t\t\treturn fmt.Errorf(\"timeout waiting for second stream to finish\")\n\t\tcase <-s.doneChan:\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn nil\n}\nfunc (s *bidirectionalStreamStatus) isAnyDone() bool {\n\treturn atomic.LoadUint32(&s.anyDone) > 0\n}\n\n// Pipe copies copy data to & from provided io.ReadWriters.\nfunc Pipe(tunnelConn, originConn io.ReadWriter, log *zerolog.Logger) {\n\t_ = PipeBidirectional(NopCloseWriterAdapter(tunnelConn), NopCloseWriterAdapter(originConn), 0, log)\n}\n\n// PipeBidirectional copies data to two unidirectional streams. It is a special case of Pipe where it receives a concept that allows for Read and Write side to be closed independently.\n// The main difference is that when piping data from a reader to a writer, if EOF is read, then this implementation propagates the EOF signal to the destination/writer by closing the write side of the","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/cloudflare/cloudflared/blob/2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f/stream/stream.go#L56-L92","documentation":"The stream `wait` helper in cloudflared's bidirectional pipe waits up to maxWaitForSecondStream for the second stream (the opposite direction) to appear or for doneChan to close. If the timer fires first, it means only one side of the proxied conversation finished/started and the paired stream never arrived within the deadline, so PipeBidirectional is aborted with this error.","triggerScenarios":"PipeBidirectional is called for a connection (e.g. TCP-over-WebSocket/QUIC) where only one data stream is ever opened and the peer never initiates the second stream before maxWaitForSecondStream elapses; protocol mismatch where one side expects one stream and the other opens two.","commonSituations":"Version mismatches between cloudflared and edge on connection framing; half-open TCP connections where a client connects, sends nothing, and idles; protocols that keep a single long-lived stream (e.g. long-polling apps) tripping the second-stream timeout.","solutions":["Check that cloudflared and Cloudflare edge versions are current; older mixed versions can frame streams differently.","Verify the origin application actually uses both directions; a single-stream application design will inherently hit this wait.","Tune maxWaitForSecondStream upward if legitimate slow second streams are being cut off.","Investigate the client for half-open/idle connections and enable TCP keepalives to reap them."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// before calling: ensure a token is present and CSR is valid\ntoken, ok := os.LookupEnv(\"TUNNEL_TOKEN\")\nif !ok || token == \"\" {\n\treturn errors.New(\"missing Cloudflare token for cert signing\")\n}\nif _, err := x509.ParseCertificateRequest(csr); err != nil {\n\treturn fmt.Errorf(\"invalid CSR: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"cert, err := sshgen.SignCert(ctx, client, hostnames, csr)\nif err != nil {\n\tvar status int\n\tif _, scanErr := fmt.Sscanf(err.Error(), \"%d:\", &status); scanErr == nil && (status == 429 || status >= 500) {\n\t\t// transient: retry with backoff\n\t}\n\treturn err\n}","preventionTips":["Keep the Cloudflare API/tunnel token valid and scoped before running ssh-gen.","Validate CSR key type and principals before submission.","Handle 429/5xx statuses with retries; treat 400/401/403 as configuration fixes."],"tags":["streaming","timeout","network"],"backgroundTag":"request-timeout","analyzedSha":"2253eeeb25a44a713a4b60b8ba1e1b3f377d1a0f","analyzedAt":"2026-09-06T04:14:33.757Z","contentChangedAt":"2026-09-06T04:14:33.757Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}