gitroomhq/postiz-app · error · BadBody
Failed to upload the media file
Error message
Failed to upload the media file
What it means
After downloading the media bytes, the provider PUTs them to the write_url Skool returned from its create-file step. If the PUT response is not OK, a BadBody error 'Failed to upload the media file' is thrown, deliberately preventing the post from publishing with an attachment id that has no stored bytes.
Source
Thrown at libraries/nestjs-libraries/src/integrations/social/skool.provider.ts:276
});
if (!fileResponse.ok || !fileResponse.body) {
throw new Error(`Failed to fetch media: ${fileResponse.statusText}`);
}
const uploadResponse = await fetch(createFileResponse.write_url, {
method: 'PUT',
headers: {
'Content-Type': createFileResponse.content_type,
'Content-Length': String(contentLength),
'x-amz-acl': createFileResponse.acl,
},
body: fileResponse.body,
// Required by undici when streaming a request body.
duplex: 'half',
} as any);
// A rejected PUT would otherwise publish the post with an empty
// attachment - the file id exists but no bytes were stored.
if (!uploadResponse.ok) {
throw new BadBody(
this.identifier,
await uploadResponse.text().catch(() => '{}'),
'{}',
'Failed to upload the media file'
);
}
fileIds.push(createFileResponse.file.id);
}
return fileIds.join(',');
}
async post(
id: string,
accessToken: string,
postDetails: PostDetails[],
integration: IntegrationView on GitHub (pinned to 0f1647f749)
Solutions
- Retry the post - transient presign/storage failures usually succeed on retry
- Confirm the Content-Type sent in create-file matches the PUT header
- Check contentLength accuracy; a wrong Content-Length causes a truncated stream rejection
- Capture uploadResponse.text() in logs to see the storage host's reason
Example fix
// before
if (!uploadResponse.ok) { throw new BadBody(...); }
// after: retry the PUT once before failing
let uploadResponse = await doPut();
if (!uploadResponse.ok) { await timer(1000); uploadResponse = await doPut(); }
if (!uploadResponse.ok) { throw new BadBody(...); } Defensive patterns
Strategy: retry
Try / catch
try { await uploadMediaToSkool(...); } catch (e) { if (e instanceof BadBody && /upload the media file/.test(e.message)) return retryOnce(); throw e; } Prevention
- Stream the PUT promptly after the create-file call so the write_url doesn't expire
- Keep Content-Type consistent between metadata and PUT
- Retry the whole upload flow once on storage 5xx
When it happens
Trigger: Skool's write_url (presigned upload endpoint) rejects the PUT: expired signature, wrong Content-Type/Content-Length headers, body stream aborted mid-upload, or a 5xx from the storage host.
Common situations: Slow networks causing the presigned URL to expire before the streamed body finishes, content-type mismatch between the create-file metadata and the actual PUT headers, or transient Skool storage errors.
Related errors
- Failed to fetch media: ${fileResponse.statusText}
- Reddit media upload did not return a location
- Could not determine the media size for upload
- Could not determine the media size for upload
- HTTP error! status: ${response.status}
AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27).
Data as JSON: /api/errors/b8e58b3d9fcce39c.
Report an issue: GitHub.