{"record":{"id":"d7662a9fef85fbb5","repo":"antiwork/gumroad","slug":"we-could-not-find-your-uploaded-files-please-uplo","errorCode":null,"errorMessage":"We could not find your uploaded files. Please upload them again.","messagePattern":"We could not find your uploaded files\\. Please upload them again\\.","errorType":"exception","errorClass":"DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError","httpStatus":null,"severity":"warning","filePath":"app/controllers/purchases/dispute_evidence_controller.rb","lineNumber":132,"sourceCode":"        :customer_communication_file_signed_blob_id,\n        customer_communication_file_signed_blob_ids: []\n      )\n    end\n\n    # Asset bundles and server code don't deploy atomically, so a seller holding the old\n    # JS bundle still submits the singular param. One file — from either param shape —\n    # keeps today's direct-attach behaviour, including the PNG conversion below.\n    def customer_communication_file_signed_blob_ids\n      signed_blob_ids = Array.wrap(dispute_evidence_params[:customer_communication_file_signed_blob_ids]).compact_blank\n      signed_blob_ids.presence || Array.wrap(dispute_evidence_params[:customer_communication_file_signed_blob_id].presence)\n    end\n\n    # A signed id that no longer resolves means the upload expired or the seller is retrying a\n    # submission whose blobs were already consumed — an alert, not a 500.\n    def customer_communication_file_blobs\n      customer_communication_file_signed_blob_ids.map { ActiveStorage::Blob.find_signed!(_1) }\n    rescue ActiveSupport::MessageVerifier::InvalidSignature, ActiveRecord::RecordNotFound\n      raise DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError,\n            \"We could not find your uploaded files. Please upload them again.\"\n    end\n\n    def set_dispute_evidence\n      disputable = @purchase.charge.presence || @purchase\n      @dispute_evidence = disputable.dispute.dispute_evidence\n    end\n\n    def check_if_needs_redirect\n      message = \\\n        if @dispute_evidence.not_seller_contacted?\n          # The feature flag was not enabled when the email was sent out\n          \"You are not allowed to perform this action.\"\n        elsif @dispute_evidence.resolved?\n          \"Additional information can no longer be submitted for this dispute.\"\n        elsif !@dispute_evidence.accepting_evidence?\n          # Window elapsed but the row is not resolved yet: FightDisputeJob is forwarding it, so\n          # anything saved now would arrive too late to be part of the submission.","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/controllers/purchases/dispute_evidence_controller.rb#L114-L150","documentation":"Raised as `DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError` in dispute_evidence_controller.rb:129-134 when `ActiveStorage::Blob.find_signed!` fails on any submitted signed blob id — `ActiveSupport::MessageVerifier::InvalidSignature` (tampered/mismatched-purpose id) or `RecordNotFound` (blob record gone). Per the code comment this means the upload expired or the seller is retrying a submission whose blobs were already consumed; it is deliberately a user-facing alert redirect, not a 500.","triggerScenarios":"Seller uploads dispute-evidence files, waits past the signed-id validity window, then submits; double-submitting the same form (first successful submission purges the input blobs at line 85, so the retry's signed ids resolve to purged blobs); a signed id generated in one environment/secret context posted to another.","commonSituations":"Long dispute-evidence drafting sessions before submit; browser back-button resubmits after success; deploys rotating secret_key_base (invalidating signed ids); test fixtures signing ids with the wrong secret or purpose.","solutions":["Re-upload the files on the dispute evidence form and submit again — fresh signed ids resolve fine.","Avoid double submits: after the see-other redirect, reload the page instead of re-POSTing the old form.","Keep the interval between upload and submit short; don't leave the form open overnight with pre-uploaded files.","If it fails immediately after a deploy, confirm secret_key_base (and any signed-id purpose settings) matches what signed the ids."],"exampleFix":"# before — resubmitting consumed signed ids\ndispute_evidence_params[:customer_communication_file_signed_blob_ids] # => blobs purged by first submit\n\n# after — re-upload, then submit with fresh signed ids\nnew_blobs = files.map { ActiveStorage::Blob.create_and_upload!(io: _1, filename: _1.original_filename) }\ndispute_evidence.update(customer_communication_file_signed_blob_ids: new_blobs.map(&:signed_id))","handlingStrategy":"retry","validationCode":"# before submitting, verify every signed id still resolves\nvalid = signed_blob_ids.all? { ActiveStorage::Blob.find_signed(_1).present? }\nsubmit_dispute_evidence(signed_blob_ids) if valid","typeGuard":"def resolvable_signed_blob_ids?(ids)\n  ids.all? { ActiveStorage::Blob.find_signed(_1).present? }\nend","tryCatchPattern":"begin\n  update # submits evidence\nrescue DisputeEvidence::MergeCustomerCommunicationFilesService::MergeError => e\n  redirect_to purchase_dispute_evidence_path(@purchase_route_id), alert: e.message # \"upload them again\"\nend","preventionTips":["Upload the files close in time to the submit; don't stage uploads hours early.","After a successful submission, never re-POST the old form (input blobs are purged).","Disable the submit button on first click to stop double submits.","Keep secret_key_base stable across deploys, or accept that staged uploads invalidate."],"tags":["activestorage","signed-id","disputes","file-upload","retry"],"backgroundTag":"activestorage-signed-id-invalid","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}