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
- Wait for the original resume/approval request to be handled; do not resend
- Skip the resend if you receive this error — the first request is already in flight
- Cancel the suspended job (cancel_suspended_job) and restart the flow if a fresh request is needed
- 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
- Never retry resume-request calls blindly; the first send already registered
- Disable the approval button after the first click
- Check the step's state before sending a request
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
- the flow is not in a suspended state anymore
- You can't use this function in a standalone script or flow s
- This function can only be called as a flow step
- You can't use 'request_interactive_slack_approval' function
- WAC step key "{requested_key}" is already used in this workf
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/36fdd45b71dd0832.
Report an issue: GitHub.