cloudflare/pingora · error · std::io::Error

dial9 s3 bucket must not be empty

Error message

dial9 s3 bucket must not be empty

What it means

When pingora-runtime is built with the dial9-worker-s3 feature and opts.s3_upload is Some, build_dial9_runtime() validates the S3 upload settings before wiring the background uploader (lib.rs:365). The bucket name is trimmed and must be non-empty, otherwise construction returns ErrorKind::InvalidInput with 'dial9 s3 bucket must not be empty'.

Source

Thrown at pingora-runtime/src/lib.rs:365

    let writer = RotatingWriter::builder()
        .base_path(opts.trace_path.clone())
        .max_file_size(opts.max_file_size)
        .max_total_size(opts.max_total_size)
        .maybe_rotation_period(opts.rotation_period)
        .build()?;

    let mut traced = TracedRuntime::builder()
        .with_trace_path(opts.trace_path.clone())
        .with_runtime_name(runtime_name)
        .with_task_tracking(opts.task_tracking);
    if let Some(worker_poll_interval) = opts.worker_poll_interval {
        traced = traced.with_worker_poll_interval(worker_poll_interval);
    }

    #[cfg(feature = "dial9-worker-s3")]
    if let Some(s3_upload) = &opts.s3_upload {
        if s3_upload.bucket.trim().is_empty() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "dial9 s3 bucket must not be empty",
            ));
        }
        if s3_upload.service_name.trim().is_empty() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "dial9 s3 service_name must not be empty",
            ));
        }
        let s3_config = dial9_tokio_telemetry::background_task::s3::S3Config::builder()
            .bucket(s3_upload.bucket.clone())
            .service_name(s3_upload.service_name.clone())
            .maybe_prefix(s3_upload.prefix.clone())
            .maybe_region(s3_upload.region.clone())
            .maybe_instance_path(s3_upload.instance_path.clone());
        let traced = traced.with_s3_uploader(s3_config.build());
        if let Some(client) = s3_upload.client.clone() {

View on GitHub (pinned to 0046038bd4)

Solutions

  1. Set a real bucket name, e.g. bucket: "my-trace-bucket".to_string()
  2. Also set a non-empty service_name (checked immediately after) and prefer explicit region/credentials configuration
  3. Fail config loading early when the S3 block is present but bucket/service_name are blank

Example fix

// before
s3_upload: Some(S3Upload { bucket: String::new(), service_name: "s3".into(), ..Default::default() })

// after
s3_upload: Some(S3Upload { bucket: "my-trace-bucket".into(), service_name: "s3".into(), ..Default::default() })
Defensive patterns

Strategy: validation

Validate before calling

if let Some(s3) = &opts.s3_upload {
    if s3.bucket.trim().is_empty() {
        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "s3 bucket must be set"));
    }
}

Prevention

When it happens

Trigger: Dial9RuntimeOpts with s3_upload = Some(S3Upload { bucket: "" or " ", .. }) while the dial9-worker-s3 feature is compiled in; the check runs during dial9 runtime construction at startup.

Common situations: S3 upload enabled in config but the bucket key left blank; bucket populated from an env var that is unset in the environment being deployed; whitespace-only values from templated config files.

Related errors


AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16). Data as JSON: /api/errors/ee4cd6147cbc0b82. Report an issue: GitHub.