{"record":{"id":"231c63ffa6ae2432","repo":"we-promise/sure","slug":"pdf-content-is-required","errorCode":null,"errorMessage":"PDF content is required","messagePattern":"PDF content is required","errorType":"exception","errorClass":"Provider::Anthropic::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/anthropic/bank_statement_extractor.rb","lineNumber":20,"sourceCode":"  include Provider::Anthropic::Concerns::UsageRecorder\n\n  TOOL_NAME = \"report_bank_statement\".freeze\n\n  # Mirrors Provider::Anthropic::PdfProcessor::MAX_PDF_BYTES.\n  MAX_PDF_BYTES = 32 * 1024 * 1024\n\n  attr_reader :client, :model, :pdf_content, :langfuse_trace, :family\n\n  def initialize(client:, model:, pdf_content:, langfuse_trace: nil, family: nil)\n    @client = client\n    @model = model\n    @pdf_content = pdf_content\n    @langfuse_trace = langfuse_trace\n    @family = family\n  end\n\n  def extract\n    raise Provider::Anthropic::Error, \"PDF content is required\" if pdf_content.blank?\n    if pdf_content.bytesize > MAX_PDF_BYTES\n      raise Provider::Anthropic::Error,\n            \"PDF exceeds Anthropic's 32 MB limit (#{pdf_content.bytesize} bytes)\"\n    end\n\n    span = langfuse_trace&.span(name: \"extract_bank_statement_api_call\", input: {\n      model: model,\n      pdf_size: pdf_content.bytesize\n    })\n\n    response = client.messages.create(\n      model: model,\n      max_tokens: max_tokens,\n      system_: instructions,\n      messages: [ { role: \"user\", content: user_content } ],\n      tools: [ output_tool ],\n      tool_choice: { type: \"tool\", name: TOOL_NAME, disable_parallel_tool_use: true }\n    )","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/anthropic/bank_statement_extractor.rb#L2-L38","documentation":"Provider::Anthropic::BankStatementExtractor#extract raises \"PDF content is required\" when pdf_content is blank (nil or empty string). The extractor sends the raw PDF bytes as a native document block to the Messages API, so empty content has nothing to send; the guard runs before the API call, the Langfuse span, and size check.","triggerScenarios":"Extract is called with pdf_content: nil — an upload whose file read returned nil, a statement record with an empty attachment, or an upstream pipeline that passes the filename instead of contents.","commonSituations":"Controller/job reads the attachment before it finished uploading; tempfile closed and re-read returns nil; code passes pdf_path or File object where bytes are expected; statement import from a URL that downloaded zero bytes.","solutions":["Fix the caller to pass actual binary content (pdf.download / File.binread(path)), not a path or File handle.","Guard upstream: verify the uploaded statement has non-zero bytes before enqueuing the extraction job.","If the attachment itself is empty, re-import/re-upload the statement."],"exampleFix":"# before\nextractor.extract # pdf_content came from statement.file.path (a String path)\n# => PDF content is required\n\n# after\nextractor = BankStatementExtractor.new(client:, model:, pdf_content: statement.file.download)\nextractor.extract","handlingStrategy":"validation","validationCode":"pdf_bytes = statement.file.download\nif pdf_bytes.blank?\n  return error(\"Statement file is empty — re-upload\")\nend\nextractor.extract # constructed with pdf_content: pdf_bytes","typeGuard":null,"tryCatchPattern":"begin\n  extractor.extract\nrescue Provider::Anthropic::Error => e\n  # input problem: re-upload, don't retry\n  statement.mark_import_failed(reason: e.message)\nend","preventionTips":["Validate attachment presence and non-zero size at upload time (model validation).","Always pass bytes (.download / File.binread), never paths or File objects.","Fail the import record with a clear reason instead of raising into the job."],"tags":["anthropic","llm","pdf","bank-statement","precondition"],"backgroundTag":"missing-required-parameter","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}