From 12730cc0ccf4688c9ad8fe3c458121659c32caea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Robles?= Date: Fri, 16 Oct 2026 17:22:03 +0000 Subject: [PATCH 1/2] Harvest: fail a voter import whose task can't be sent to the queue When send_task failed, import_users_f returned the task as if it had been sent, so the import stayed In Progress with nothing reporting the failure. It now logs the error and marks the task Failed with the reason, only while it is still in progress, so a worker's final status is not overwritten. The response still carries the task, so the portal's widget follows it and shows the failure. The message says it is not known whether the task reached the queue: a failed send doesn't prove the broker never got it, and nothing confirms delivery either way. Parent issue: https://github.com/sequentech/meta/issues/14271 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0129Cs8ygVE7UH6N1XzXk8WP --- packages/harvest/src/routes/users.rs | 35 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/harvest/src/routes/users.rs b/packages/harvest/src/routes/users.rs index 93aa2d8d865..d9d73cf5b16 100644 --- a/packages/harvest/src/routes/users.rs +++ b/packages/harvest/src/routes/users.rs @@ -29,7 +29,7 @@ use serde::Deserialize; use serde::Serialize; use std::collections::{HashMap, HashSet}; use std::env; -use tracing::{info, instrument}; +use tracing::{error, info, instrument}; use uuid::Uuid; use windmill::postgres::election_event::get_election_event_by_id; use windmill::services::cast_votes::get_users_with_vote_info; @@ -1867,20 +1867,37 @@ pub async fn import_users_f( .may_write_secret_attributes .then(|| ElectoralLogAdminContext::from_claims(&claims)); - let _celery_task = match celery_app + if let Err(err) = celery_app .send_task(import_users::import_users::new( task_input, task_execution.clone(), )) .await { - Ok(celery_task) => celery_task, - Err(_) => { - return Ok(Json(ImportUsersOutput { - task_execution: task_execution.clone(), - })); - } - }; + error!( + "Failed to send IMPORT_USERS task {}: {err:?}", + task_execution.id + ); + // The send may have failed after the task reached the queue, so only a + // task still in progress is failed and a worker's final status is kept. + // The portal's task widget reads the task by id and shows this log. + update_export_fail( + &task_execution, + &format!( + "Could not send the Import Voters task to the task queue: {err:?}. It is not known whether the task reached the queue, so check the voters before importing the file again." + ), + ) + .await + .map_err(|update_error| { + ( + Status::InternalServerError, + format!( + "Error sending Import Voters task: {err:?}; failed to mark the task as failed: {update_error:?}" + ), + ) + })?; + return Ok(Json(ImportUsersOutput { task_execution })); + } info!("Sent IMPORT_USERS task {}", task_execution.id); From 4669993f45cc36b6d57811559eac3f824df4844b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Robles?= Date: Sun, 18 Oct 2026 02:45:25 +0000 Subject: [PATCH 2/2] fix: surface voter import action failures in the portal Notify when the import action rejects and reflect local failure status while a widget has no task id. Once assigned, retain the persisted task status so a worker success is never replaced by local failure. Parent issue: https://github.com/sequentech/meta/issues/14271 --- packages/admin-portal/src/components/Widget.tsx | 6 ++++++ packages/admin-portal/src/resources/User/ListUsers.tsx | 1 + 2 files changed, 7 insertions(+) diff --git a/packages/admin-portal/src/components/Widget.tsx b/packages/admin-portal/src/components/Widget.tsx index dff4562f700..de66f936106 100644 --- a/packages/admin-portal/src/components/Widget.tsx +++ b/packages/admin-portal/src/components/Widget.tsx @@ -124,6 +124,12 @@ export const Widget: React.FC = ({ : globalSettings.QUERY_POLL_INTERVAL_MS, }) + useEffect(() => { + if (!taskId) { + setTaskDataStatus(status) + } + }, [status, taskId]) + useEffect(() => { if (taskData && taskData.sequent_backend_tasks_execution.length > 0) { const task = taskData.sequent_backend_tasks_execution[0] diff --git a/packages/admin-portal/src/resources/User/ListUsers.tsx b/packages/admin-portal/src/resources/User/ListUsers.tsx index e65c07f4531..3b0580eaa3e 100644 --- a/packages/admin-portal/src/resources/User/ListUsers.tsx +++ b/packages/admin-portal/src/resources/User/ListUsers.tsx @@ -1161,6 +1161,7 @@ export const ListUsers: React.FC = ({aside, electionEventId, ele console.log(``) console.log(`Error importing voters: ${err}`) updateWidgetFail(currWidget.identifier) + notify(t("electionEventScreen.import.importVotersError"), {type: "error"}) } }