we-promise/sure · error · Provider::Openai::Error

Could not convert PDF to images

Error message

Could not convert PDF to images

What it means

Raised by Provider::Openai::PdfProcessor#process_with_vision when convert_pdf_to_images returns an empty array. That helper writes the bytes to a temp file, shells out via system("pdftoppm", "-png", "-r", "150", ...) — poppler-utils — and globs page-*.png; every exception is rescued to [] and system failures (nonzero exit, command not found) simply leave no files. So the two root causes are pdftoppm missing from PATH (typical slim Docker images) and pdftoppm failing on the PDF itself (encrypted, corrupt, or unsupported).

Source

Thrown at app/models/provider/openai/pdf_processor.rb:154

      text_parts = []

      reader.pages.each_with_index do |page, index|
        text_parts << "--- Page #{index + 1} ---"
        text_parts << page.text
      end

      text_parts.join("\n\n")
    rescue => e
      Rails.logger.error("Failed to extract text from PDF: #{e.message}")
      nil
    end

    def process_with_vision
      effective_model = model.presence || Provider::Openai::DEFAULT_MODEL

      # Convert PDF to images using pdftoppm
      images_base64 = convert_pdf_to_images
      raise Provider::Openai::Error, "Could not convert PDF to images" if images_base64.blank?

      # Build message content with images (max 5 pages to avoid token limits)
      content = []
      images_base64.first(5).each do |img_base64|
        content << {
          type: "image_url",
          image_url: {
            url: "data:image/png;base64,#{img_base64}",
            detail: "low"
          }
        }
      end
      content << {
        type: "text",
        text: "Please analyze this PDF document (#{images_base64.size} pages total, showing first #{[ images_base64.size, 5 ].min}) and respond with valid JSON only."
      }

      # Note: response_format is not compatible with vision, so we ask for JSON in the prompt

View on GitHub (pinned to e69894adb9)

Solutions

  1. Verify pdftoppm exists in the runtime image: docker run --rm <image> which pdftoppm; add poppler-utils (Debian/Ubuntu: apt-get install -y poppler-utils; Alpine: apk add poppler-utils).
  2. Capture pdftoppm's exit status and stderr in convert_pdf_to_images instead of discarding them, so missing-binary vs bad-PDF is distinguishable.
  3. Fail fast at boot/health check: raise a clear configuration error if system('which', 'pdftoppm') fails.
  4. For encrypted PDFs, decrypt before the vision path.

Example fix

# before
system("pdftoppm", "-png", "-r", "150", pdf_path, output_prefix)
image_files = Dir.glob(File.join(tmpdir, "page-*.png")).sort

# after
ok = system("pdftoppm", "-png", "-r", "150", pdf_path, output_prefix, err: "/tmp/pdftoppm.err")
Rails.logger.error("pdftoppm failed (#{$?.exitstatus}): #{File.read('/tmp/pdftoppm.err')}") unless ok
image_files = Dir.glob(File.join(tmpdir, "page-*.png")).sort
Defensive patterns

Strategy: validation

Validate before calling

raise RuntimeError, "pdftoppm (poppler-utils) is not installed" unless system("which", "pdftoppm", out: File::NULL, err: File::NULL)

Try / catch

begin
  processor.process
rescue Provider::Openai::Error => e
  raise unless e.message.include?("Could not convert PDF to images")
  notify_ops("vision PDF path broken: check poppler-utils in the runtime image")
end

Prevention

When it happens

Trigger: Deploying to alpine/slim Docker without installing poppler-utils (system returns nil, zero PNGs); pdftoppm erroring 'Incorrect password' on an encrypted PDF; malformed PDF making pdftoppm exit nonzero; pdf_content blank hitting the early return [].

Common situations: Works on the dev Mac (poppler preinstalled via brew), fails in production container; CI pipeline lacking system packages; encrypted statements reaching the vision fallback; disk-full temp dirs making writes fail silently inside the rescue.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/b32f629cb0a94ed6. Report an issue: GitHub.