windmill-labs/windmill · error

resume request already sent

Error message

resume request already sent

What it means

The flow resume (approval) endpoint refuses to send a second resume request for the same suspended step. A row recording that a resume/approval request was already dispatched exists, so a duplicate request is rejected to prevent double-resumption or duplicate approval emails.

Source

Thrown at backend/windmill-api/src/jobs.rs:5124

    let (flow_info, is_flow_level, is_wac) = get_flow_info_for_resume(job_id, &db).await?;

    // HMAC secret = full capability. Skip approval_conditions checks: possession of the full
    // resume URL is the authorization (it is only disclosed to intended approvers, e.g. when a
    // step returns it). Identity-based rules, including self_approval_disabled, are enforced by
    // the resume_suspended endpoint instead.

    let exists = sqlx::query_scalar!(
        r#"
            SELECT EXISTS (SELECT 1 FROM resume_job WHERE id = $1)
            "#,
        Uuid::from_u128(job_id.as_u128() ^ resume_id as u128),
    )
    .fetch_one(&db)
    .await?
    .unwrap_or(false);

    if exists {
        return Err(anyhow::anyhow!("resume request already sent").into());
    }

    let approver_value = if authed.as_ref().is_none()
        || (approver
            .approver
            .clone()
            .is_some_and(|x| x != "".to_string()))
    {
        approver.approver
    } else {
        authed.as_ref().map(|x| x.username.clone())
    };
    let mut tx: Transaction<'_, Postgres> = db.begin().await?;

    // Inside the transaction that inserts the row and moves the suspend counter:
    // validating earlier would let the workflow resolve this step and suspend on the
    // next one in between, so a stale request would wake that later step instead.
    if is_wac {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Wait for the original resume/approval request to be handled; do not resend
  2. Skip the resend if you receive this error — the first request is already in flight
  3. Cancel the suspended job (cancel_suspended_job) and restart the flow if a fresh request is needed
  4. Check the approval step's state in the flow runs UI before sending another request

Example fix

// before
await api.resumeRequest(wId, flowId, resumeId, payload);
await api.resumeRequest(wId, flowId, resumeId, payload); // throws
// after
const alreadySent = await checkResumeSent(flowId, resumeId);
if (!alreadySent) await api.resumeRequest(wId, flowId, resumeId, payload);
Defensive patterns

Strategy: validation

Validate before calling

// client-side: send at most one resume request per suspended step
const sent = new Set();
function sendResume(flowId, resumeId, payload) {
  const key = flowId + ':' + resumeId;
  if (sent.has(key)) return Promise.resolve('skipped');
  sent.add(key);
  return client.resume(flowId, resumeId, payload);
}

Prevention

When it happens

Trigger: Calling the resume/approval request endpoint twice for the same suspended flow step before the first resume is consumed; retrying an approval request that already succeeded; two users clicking 'send approval email' concurrently.

Common situations: Duplicate clicks on an approval button; automated retries after a slow email send; replaying an API request without idempotency awareness.

Related errors


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