block/buzz · critical · anyhow::Error

invalid media config

Error message

invalid media config: {e}

What it means

Fatal startup error: MediaConfig::validate() rejected the media/S3 configuration before MediaStorage construction. The checks are concrete: public_base_url must end with /media and must not end with /; max_image_bytes > 0; max_gif_bytes in (0, max_image_bytes]; max_video_bytes and max_file_bytes > 0; an upload IP header without upload records enabled is rejected; a port header without an IP header is rejected.

Solutions

  1. Set BUZZ_MEDIA_PUBLIC_BASE_URL to a URL ending exactly with /media and no trailing slash (e.g. https://cdn.example.com/media)
  2. Make all size limits positive integers, with max_gif_bytes <= max_image_bytes
  3. Either enable BUZZ_MEDIA_UPLOAD_RECORDS=true when an upload IP header is configured, or unset the header
  4. Unset the port header unless the IP header is also set

Example fix

# before
export BUZZ_MEDIA_PUBLIC_BASE_URL=https://cdn.example.com
export BUZZ_MAX_IMAGE_BYTES=0

# after
export BUZZ_MEDIA_PUBLIC_BASE_URL=https://cdn.example.com/media
export BUZZ_MAX_IMAGE_BYTES=52428800
Defensive patterns

Strategy: validation

Validate before calling

# Deploy-time guard:
: "${BUZZ_MEDIA_PUBLIC_BASE_URL:?}"
case "$BUZZ_MEDIA_PUBLIC_BASE_URL" in
  */media) ;;               # ok, no trailing slash beyond /media
  *) echo 'BUZZ_MEDIA_PUBLIC_BASE_URL must end with /media'; exit 1 ;;
esac

Prevention

When it happens

Trigger: BUZZ_MEDIA_PUBLIC_BASE_URL missing the /media suffix or carrying a trailing slash; a size limit var set to 0; max GIF size larger than max image size; BUZZ_MEDIA_UPLOAD_IP_HEADER set while BUZZ_MEDIA_UPLOAD_RECORDS is not enabled; BUZZ_MEDIA_UPLOAD_PORT_HEADER set without the IP header.

Common situations: Pointing public_base_url at a CDN root instead of the /media path; tuning size limits to "unlimited" by entering 0 (which actually means invalid); enabling IP reporting without turning on the records that store it.

Related errors


AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-08-20). Data as JSON: /api/errors/862c86efb6a85b2f. Report an issue: GitHub.

Appendix: source

Thrown at crates/buzz-relay/src/main.rs:512

        .unwrap_or(&config.database_url);
    let search_pool = sqlx::postgres::PgPoolOptions::new()
        .connect(search_db_url)
        .await
        .map_err(|e| anyhow::anyhow!("Search DB connection failed: {e}"))?;
    let search_metrics_pool = search_pool.clone();
    let search = SearchService::new(search_pool);
    info!(
        replica = config.read_database_url.is_some(),
        "Search service ready (Postgres FTS)"
    );

    let workflow_config = buzz_workflow::WorkflowConfig::default();
    let workflow_engine = Arc::new(WorkflowEngine::new(db.clone(), workflow_config));

    config
        .media
        .validate()
        .map_err(|e| anyhow::anyhow!("invalid media config: {e}"))?;
    let media_storage = buzz_media::MediaStorage::new(&config.media)
        .map_err(|e| anyhow::anyhow!("failed to initialize media storage: {e}"))?;
    info!("Media storage connected");

    let (app_state, audit_shutdown) = AppState::new(
        config.clone(),
        db,
        redis_health_pool,
        audit,
        pubsub,
        auth,
        search,
        Arc::clone(&workflow_engine),
        relay_keypair,
        media_storage,
    );
    let state = Arc::new(app_state);

View on GitHub (pinned to ef2aa1ae38)