windmill-labs/windmill · error

Module '{}': Exponential backoff base (seconds) must be grea

Error message

Module '{}': Exponential backoff base (seconds) must be greater than 0. A base of 0 would cause immediate retries.

What it means

Flow validation checks each module's retry policy. An exponential backoff policy with attempts > 0 but a base of 0 seconds is invalid because the base is the initial delay multiplier — with 0 every retry fires immediately with no backoff, so validation rejects the flow.

Source

Thrown at backend/windmill-types/src/flows.rs:212

            schema: self.schema,
            tag: self.tag,
            dedicated_worker: self.dedicated_worker,
            timeout: self.timeout,
            deployment_message: self.deployment_message,
            visible_to_runner_only: self.visible_to_runner_only,
            on_behalf_of_email: self.on_behalf_of_email,
            on_behalf_of: self.on_behalf_of,
            preserve_on_behalf_of: self.preserve_on_behalf_of,
            ws_error_handler_muted: self.ws_error_handler_muted,
            labels: self.labels,
            skip_draft_deletion: self.skip_draft_deletion,
        }
    }
}

fn validate_retry(retry: &Retry, module_id: &str) -> anyhow::Result<()> {
    if retry.exponential.attempts > 0 && retry.exponential.seconds == 0 {
        return Err(anyhow::anyhow!(
            "Module '{}': Exponential backoff base (seconds) must be greater than 0. A base of 0 would cause immediate retries.",
            module_id
        ));
    }
    Ok(())
}

/// Script/sub-flow step references must be workspace paths (`u/`, `f/`, `g/`) or a hub
/// reference (`hub/`). Empty is tolerated for intermediate/incomplete steps. This blocks
/// absolute or local filesystem paths (e.g. `/tmp/.../ops/scripts/...` baked in by a
/// `wmill sync push` from a feature-branch checkout) from being persisted into a flow,
/// where they silently mis-resolve to an unrelated script at runtime (#9751).
fn is_workspace_runnable_path(path: &str) -> bool {
    path.is_empty()
        || path.starts_with("u/")
        || path.starts_with("f/")
        || path.starts_with("g/")
        || path.starts_with("hub/")

View on GitHub (pinned to e474e8803c)

Solutions

  1. Set retry.exponential.seconds to a positive value (e.g. 1) whenever attempts > 0
  2. If retries aren't wanted, set exponential.attempts to 0 instead of leaving seconds at 0
  3. Validate the flow JSON locally before deploying

Example fix

// before
"retry": {"exponential": {"attempts": 3, "seconds": 0}}
// after
"retry": {"exponential": {"attempts": 3, "seconds": 1}}
Defensive patterns

Strategy: validation

Validate before calling

function validateRetry(retry) {
  if (retry?.exponential?.attempts > 0 && retry.exponential.seconds <= 0)
    throw new Error("exponential backoff base (seconds) must be > 0 when attempts > 0");
}

Prevention

When it happens

Trigger: Saving or validating a flow (validate_flow_value, called on flow create/update) where any module has retry.exponential.attempts set to a positive number while retry.exponential.seconds is 0.

Common situations: Building flows via API/CLI where the retry object is partially filled (attempts set, seconds left at default 0), UI form bugs, or copying retry configs between fixed-interval and exponential retry types.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/eeb01e9427bebe50. Report an issue: GitHub.