sinatra/sinatra · error · IOError

not opened for reading

Error message

not opened for reading

What it means

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.

Source

Thrown at sinatra-contrib/lib/sinatra/streaming.rb:187

        closed?
      end

      def external_encoding
        Encoding.find settings.default_encoding
      rescue NameError
        settings.default_encoding
      end

      def settings
        app.settings
      end

      def rewind
        @pos = @lineno = 0
      end

      def not_open_for_reading(*)
        raise IOError, 'not opened for reading'
      end

      alias bytes         not_open_for_reading
      alias eof?          not_open_for_reading
      alias eof           not_open_for_reading
      alias getbyte       not_open_for_reading
      alias getc          not_open_for_reading
      alias gets          not_open_for_reading
      alias read          not_open_for_reading
      alias read_nonblock not_open_for_reading
      alias readbyte      not_open_for_reading
      alias readchar      not_open_for_reading
      alias readline      not_open_for_reading
      alias readlines     not_open_for_reading
      alias readpartial   not_open_for_reading
      alias sysread       not_open_for_reading
      alias ungetbyte     not_open_for_reading
      alias ungetc        not_open_for_reading

View on GitHub (pinned to cb22afd790)

Solutions

  1. Do not read from a Sinatra stream - it is write-only by design; track completion via closed? instead of eof?.
  2. Buffer what you need into a local String/StringIO and read from that, writing the result to the stream.
  3. If a library insists on reading, pass it a Tempfile/StringIO and stream the tempfile's contents out.
  4. Wrap unmodifiable third-party read calls in rescue IOError with a sensible fallback.

Example fix

# before
stream do |out|
  chunk = out.read   # write-only stream -> IOError
end

# after
stream do |out|
  buffer = StringIO.new
  chunk = buffer.read
  out.write chunk
end
Defensive patterns

Strategy: try-catch

Validate before calling

raise IOError, 'stream is write-only' if %i[read gets getc eof? readlines].any? { |m| caller_locations(1,1)[0].label == m.to_s }

Type guard

readable_io = ->(io) { io.respond_to?(:read) && !io.is_a?(Sinatra::Streaming::Stream) }

Try / catch

begin
  data = out.read
rescue IOError
  data = nil # write-only stream; nothing to read
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of sinatra/sinatra@cb22afd790 (2026-08-04). Data as JSON: /data/errors/c158b9969c8465d5.json. Report an issue: GitHub.