UploadedFile already holds the whole attachment in memory; getvalue() made a second full copy of each of the 230 files before writing. Copy through the file interface instead. Smoke tests pin the progress callback contract: once per XML file, including unreadable ones. Refs #1 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>