Anything run from the command line that touches S3-compatible storage fails with BAD_XML. That includes cron tasks and any maintenance script.
The symptom
RuntimeException: BAD_XML #0 /system/File/Amazon.php(823): IPS\Http\Response->decodeXml() #1 /system/File/Amazon.php(349): IPS\File\Amazon::makeRequest(..., 'put', ...)
In the AdminCP it often shows up first as a Locked Task warning for s3Delete, with a climbing lock count that never clears.
The cause
IPS\File\Amazon::buildBaseUrl() picks http or https from Request::i()->isSecure() — the scheme of the current request — rather than from the storage endpoint. Under CLI there is no request, so it builds http://. Cloudflare R2 answers with a 301 to https; IPS then runs its S3-specific redirect handler, which expects the body to be S3 XML containing an Endpoint element. R2 returns an HTML page, so decodeXml() throws.
Real AWS S3 hides the bug by answering http directly instead of redirecting.
The fix
Put HTTPS=on in the environment. PHP CLI copies it into $_SERVER['HTTPS'], isSecure() returns true, and the URL is built correctly.
docker exec -e HTTPS=on my_container php /path/to/script.php
Clearing a stuck task
Fixing the cause does not release an existing lock. Set running and lock_count back to zero for that task row in core_tasks.
This applies to any S3-compatible storage behind CLI: R2, Backblaze, MinIO, Wasabi.
Related application: Backup & Restore — Backup & Restore runs its archiving from a scheduled task, so it is written to survive exactly this class of CLI-context difference rather than assuming a normal web request.
Recommended Comments