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: Integration

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Retry the post - transient presign/storage failures usually succeed on retry
  2. Confirm the Content-Type sent in create-file matches the PUT header
  3. Check contentLength accuracy; a wrong Content-Length causes a truncated stream rejection
  4. 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

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


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/b8e58b3d9fcce39c. Report an issue: GitHub.