From 9cbb8a95b5b9c53ef3a4e04f7e1d8c8920f5050e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 7 Jul 2026 11:11:52 +0000 Subject: [PATCH] =?UTF-8?q?Completing=20a=20re-run=20failed=20batch=20un-f?= =?UTF-8?q?ails=20the=20task=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- orchestration/task_orchestrator.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/orchestration/task_orchestrator.py b/orchestration/task_orchestrator.py index 15915d9f1..58cb22bac 100644 --- a/orchestration/task_orchestrator.py +++ b/orchestration/task_orchestrator.py @@ -2,7 +2,7 @@ from typing import Any, Callable, Optional from uuid import UUID from domain.tasks.subtasks import SubTask -from domain.tasks.tasks import Source, Task, TaskStatus +from domain.tasks.tasks import Source, Task from repositories.tasks.subtask_repository import SubTaskRepository from repositories.tasks.task_repository import TaskRepository from utilities.private import private @@ -146,11 +146,10 @@ class TaskOrchestrator: @private def _cascade(self, task_id: UUID) -> None: task = self._tasks.get(task_id) - # FAILED is terminal: once any SubTask has failed the Task is failed and - # stays failed, so skip the (potentially large) sibling roll-up entirely — - # no need to list and re-check the SubTasks. - if task.status is TaskStatus.FAILED: - return + # Always recompute, even from FAILED: a failed batch SubTask may be + # re-run (re-sending its own inputs), and its completion must be able + # to un-fail the parent (ADR-0055). While any child is still failed, + # the recompute keeps the Task failed anyway. statuses = [s.status for s in self._subtasks.list_by_task(task_id)] task.recalculate_from_subtasks(statuses) self._tasks.save(task)