{"id":"c158b9969c8465d5","repo":"sinatra/sinatra","slug":"not-opened-for-reading","errorCode":null,"errorMessage":"not opened for reading","messagePattern":"not opened for reading","errorType":"exception","errorClass":"IOError","httpStatus":null,"severity":"error","filePath":"sinatra-contrib/lib/sinatra/streaming.rb","lineNumber":187,"sourceCode":"        closed?\n      end\n\n      def external_encoding\n        Encoding.find settings.default_encoding\n      rescue NameError\n        settings.default_encoding\n      end\n\n      def settings\n        app.settings\n      end\n\n      def rewind\n        @pos = @lineno = 0\n      end\n\n      def not_open_for_reading(*)\n        raise IOError, 'not opened for reading'\n      end\n\n      alias bytes         not_open_for_reading\n      alias eof?          not_open_for_reading\n      alias eof           not_open_for_reading\n      alias getbyte       not_open_for_reading\n      alias getc          not_open_for_reading\n      alias gets          not_open_for_reading\n      alias read          not_open_for_reading\n      alias read_nonblock not_open_for_reading\n      alias readbyte      not_open_for_reading\n      alias readchar      not_open_for_reading\n      alias readline      not_open_for_reading\n      alias readlines     not_open_for_reading\n      alias readpartial   not_open_for_reading\n      alias sysread       not_open_for_reading\n      alias ungetbyte     not_open_for_reading\n      alias ungetc        not_open_for_reading","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/sinatra/sinatra/blob/cb22afd7902b566b6eaba6c4ea89739494a65d12/sinatra-contrib/lib/sinatra/streaming.rb#L169-L205","documentation":"IOError raised by not_open_for_reading (sinatra-contrib/lib/sinatra/streaming.rb:187) and aliased to every read-side IO method: read, gets, getc, getbyte, readline, readlines, readchar, readbyte, read_nonblock, readpartial, sysread, ungetbyte, eof, eof?, bytes. Because the Sinatra stream is write-only, any read attempt is rejected outright.","triggerScenarios":"Calling out.read, out.gets, out.eof?, or any read method on a streaming response body; or handing the stream to code that reads from the IO it is given.","commonSituations":"Passing the stream to a library expecting a readable IO ( serializers, loggers, HTTP clients that read back the body), confusing the write stream with a bidirectional socket/file, or calling eof? to check completion on the wrong side.","solutions":["Do not read from a Sinatra stream - it is write-only by design; track completion via closed? instead of eof?.","Buffer what you need into a local String/StringIO and read from that, writing the result to the stream.","If a library insists on reading, pass it a Tempfile/StringIO and stream the tempfile's contents out.","Wrap unmodifiable third-party read calls in rescue IOError with a sensible fallback."],"exampleFix":"# before\nstream do |out|\n  chunk = out.read   # write-only stream -> IOError\nend\n\n# after\nstream do |out|\n  buffer = StringIO.new\n  chunk = buffer.read\n  out.write chunk\nend","handlingStrategy":"try-catch","validationCode":"raise IOError, 'stream is write-only' if %i[read gets getc eof? readlines].any? { |m| caller_locations(1,1)[0].label == m.to_s }","typeGuard":"readable_io = ->(io) { io.respond_to?(:read) && !io.is_a?(Sinatra::Streaming::Stream) }","tryCatchPattern":"begin\n  data = out.read\nrescue IOError\n  data = nil # write-only stream; nothing to read\nend","preventionTips":["Treat Sinatra streams as write-only; never call read/gets/getc/eof? on them.","Use closed?, not eof?, to detect stream completion.","Buffer into StringIO/Tempfile when a library needs to read back data.","Wrap third-party read calls in rescue IOError with a defined fallback."],"tags":["streaming","io","write-only","read"],"analyzedSha":"cb22afd7902b566b6eaba6c4ea89739494a65d12","analyzedAt":"2026-08-04T20:55:04.669Z","schemaVersion":2}