{"record":{"id":"cfcaae7e88ecf162","repo":"windmill-labs/windmill","slug":"unable-to-find-the-module","errorCode":null,"errorMessage":"unable to find the module","messagePattern":"unable to find the module","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-api/src/jobs.rs","lineNumber":5520,"sourceCode":"    // approvers are unaffected.\n    if let Some(authed) = authed.as_ref() {\n        require_job_within_run_scope(&db, authed, &w_id, &flow_id).await?;\n    }\n\n    let flow = GetQuery::new()\n        .without_logs()\n        .without_code()\n        .fetch(&db, &flow_id, &w_id)\n        .await?;\n\n    let flow_status = flow\n        .flow_status()\n        .ok_or_else(|| anyhow::anyhow!(\"unable to find the flow status in the flow job\"))?;\n    let flow_module_status = flow_status\n        .modules\n        .iter()\n        .find(|p| p.job() == Some(job))\n        .ok_or_else(|| anyhow::anyhow!(\"unable to find the module\"))?;\n\n    let trigger_email = match &flow {\n        Job::CompletedJob(job) => &job.email,\n        Job::QueuedJob(job) => &job.email,\n    };\n    conditionally_require_authed_user(\n        authed.clone(),\n        flow_status.approval_conditions.clone(),\n        trigger_email,\n    )?;\n\n    let approvers_from_status = match flow_module_status {\n        FlowStatusModule::Success { approvers, .. } => approvers.to_owned(),\n        _ => vec![],\n    };\n    let approvers = if approvers_from_status.is_empty() {\n        sqlx::query!(\n            r#\"","sourceCodeStart":5502,"sourceCodeEnd":5538,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-api/src/jobs.rs#L5502-L5538","documentation":"After parsing flow_status, the code searches its modules list for the module whose job() equals the requested job; if none matches, it throws 'unable to find the module'. This means the flow status exists but does not reference the given step/job — the resume target is not a module of that flow run.","triggerScenarios":"Using a resume link/job id belonging to a different flow run than the flow_id supplied; a flow whose module status was overwritten by a later run of the same flow; approval step ids reused across reruns.","commonSituations":"Mixing ids from two concurrent runs of the same flow; approval link from run A applied to run B; flow edited and re-deployed so module layout no longer matches old statuses.","solutions":["Confirm the job id and flow id come from the same run","Re-issue the approval/resume link from the correct run","Re-run the flow if the old run's module state no longer matches","Avoid hard-coding resume ids; derive them from the current run's flow status"],"exampleFix":"// before\nresume(wId, flowRunA_id, resumeIdFromRunB)\n// after\nresume(wId, flowRunB_id, resumeIdFromRunB)","handlingStrategy":"validation","validationCode":"// make sure job and flow ids come from the same run\nconst run = await client.getJobRun(wId, flowJobId);\nconst module = (run.flow_status?.modules ?? []).find(m => m.job === stepJobId);\nif (!module) throw new Error('step is not a module of this flow run');","typeGuard":"function stepBelongsToRun(flowStatus, stepJobId) {\n  return Boolean(flowStatus?.modules?.some(m => m.job === stepJobId));\n}","tryCatchPattern":null,"preventionTips":["Derive resume ids from the same run object as the flow id","Avoid caching resume links across reruns of a flow","Tag run ids in logs so mismatches are easy to spot"],"tags":["flow","module","flow-status","id-mismatch"],"backgroundTag":"id-mismatch","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}