{"record":{"id":"e4e1745619db67d2","repo":"sj26/mailcatcher","slug":"error-sending-message-through-websocket","errorCode":null,"errorMessage":"Error sending message through websocket","messagePattern":"Error sending message through websocket","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/mail_catcher/web/application.rb","lineNumber":90,"sourceCode":"        if MailCatcher.quittable?\n          MailCatcher.quit!\n          status 204\n        else\n          status 403\n        end\n      end\n\n      get \"/messages\" do\n        if request.websocket?\n          bus_subscription = nil\n\n          ws = Faye::WebSocket.new(request.env)\n          ws.on(:open) do |_|\n            bus_subscription = MailCatcher::Bus.subscribe do |message|\n              begin\n                ws.send(JSON.generate(message))\n              rescue => exception\n                MailCatcher.log_exception(\"Error sending message through websocket\", message, exception)\n              end\n            end\n          end\n\n          ws.on(:close) do |_|\n            MailCatcher::Bus.unsubscribe(bus_subscription) if bus_subscription\n          end\n\n          ws.rack_response\n        else\n          content_type :json\n          JSON.generate(Mail.messages)\n        end\n      end\n\n      delete \"/messages\" do\n        Mail.delete!\n        status 204","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/sj26/mailcatcher/blob/18a9cb7a79b321b2bcb51608dd793ea51255189e/lib/mail_catcher/web/application.rb#L72-L108","documentation":"While pushing a Bus event (new message, clear, remove, quit) to a subscribed browser over the /messages websocket, ws.send(JSON.generate(message)) raised (application.rb:88-91). The rescue logs the bus message as context and keeps the server running. In almost all cases the exception is a write to a socket the browser already closed, so the log line is noise rather than a fault: the on(:close) handler unsubscribes the connection, but messages pushed in the window before close-fire arrive at a dead socket.","triggerScenarios":"A browser tab viewing the MailCatcher UI is closed, refreshed, or sleeps at the same moment a new SMTP message arrives (Bus 'add' push); many browser tabs open and one disconnects; a client that opens the WS at /messages and drops without a clean close handshake; also possible, though rarer, a JSON.generate failure on an exotic message payload.","commonSituations":"Leaving MailCatcher's web UI open in a tab that gets closed right as tests send mail; CI screenshots/browser automation hitting /messages; laptop sleep/resume leaving half-dead sockets; seeing repeated `*** Error sending message through websocket: {\"type\"=>\"add\", ...}` lines in a long-running daemon's log.","solutions":["Treat isolated occurrences as benign — the subscription is cleaned up on :close and other clients still receive events; verify the UI still updates before doing anything.","Confirm the Exception line is a disconnect class (Errno::EPIPE, Errno::ECONNRESET, IOError); anything else (JSON::GeneratorError etc.) deserves investigation.","If the log floods, upgrade the mailcatcher gem — newer websocket handling checks connection state before sending.","Reduce trigger surface: close MailCatcher tabs you are not using, and avoid multiple stale tabs against one daemon."],"exampleFix":"# before (application.rb:86-92)\nbus_subscription = MailCatcher::Bus.subscribe do |message|\n  begin\n    ws.send(JSON.generate(message))\n  rescue => exception\n    MailCatcher.log_exception(\"Error sending message through websocket\", message, exception)\n  end\nend\n\n# after: only write to sockets that are still open\nbus_subscription = MailCatcher::Bus.subscribe do |message|\n  begin\n    ws.send(JSON.generate(message)) if ws.ready_state == Faye::WebSocket::OPEN\n  rescue => exception\n    MailCatcher.log_exception(\"Error sending message through websocket\", message, exception)\n  end\nend","handlingStrategy":"validation","validationCode":"# If you embed or fork the websocket handler: check state before writing\npayload = JSON.generate(message)\nws.send(payload) if ws.ready_state == Faye::WebSocket::OPEN\n# For plain users of the stock UI: verify the endpoint is live before relying on pushes\nrequire \"net/http\"\nNet::HTTP.new(\"127.0.0.1\", 1080).head(\"/messages\").code == \"200\"","typeGuard":"# Guard the socket before every push in your own Bus subscriber\ndef open_websocket?(ws)\n  ws.respond_to?(:ready_state) && ws.ready_state == Faye::WebSocket::OPEN\nend\n\nbus.on_push do |message|\n  ws.send(JSON.generate(message)) if open_websocket?(ws)\nend","tryCatchPattern":"# Keep the library's pattern: swallow disconnect-class errors, log the rest\nbegin\n  ws.send(JSON.generate(message))\nrescue Errno::EPIPE, Errno::ECONNRESET, IOError\n  # client went away; on(:close) will unsubscribe — ignore\nrescue => e\n  MailCatcher.log_exception(\"Error sending message through websocket\", message, e)\nend","preventionTips":["Close MailCatcher browser tabs when done; stale tabs are the main source of these reports.","In browser automation, tear down WebSocket connections cleanly (call ws.close()) instead of killing the page.","Keep the mailcatcher gem current so connection-state checks before send are picked up.","Treat isolated occurrences as informational; alert only on floods or non-disconnect exception classes."],"tags":["websocket","faye","connection-closed","bus","noise"],"backgroundTag":"websocket-send-failed","analyzedSha":"18a9cb7a79b321b2bcb51608dd793ea51255189e","analyzedAt":"2026-08-21T18:56:10.605Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}