From 9da38ef3e58970e9b60e58acb694a266285f00e6 Mon Sep 17 00:00:00 2001 From: abccodes Date: Tue, 23 Jun 2026 00:02:52 -0700 Subject: [PATCH] [aidan] fix/run-status: sync ongoing runs and heal stuck/interrupted runs --- backend/apps/workflows/executor.py | 13 +++++++++++++ backend/apps/workflows/scheduler.py | 8 ++++++++ frontend/src/shared/state/workflowsSlice.ts | 11 +++++++++++ 3 files changed, 32 insertions(+) diff --git a/backend/apps/workflows/executor.py b/backend/apps/workflows/executor.py index 66bc194a..f5f51011 100644 --- a/backend/apps/workflows/executor.py +++ b/backend/apps/workflows/executor.py @@ -215,6 +215,19 @@ async def execute( "last_run_id": run.id, }) + # Announce the run as running the instant it claims execution, not at the + # first step. Without this a run that fails fast (e.g. no runnable steps) or + # hasn't streamed yet never hits the Home "Ongoing runs" list, so a batch of + # missed re-runs only ever surfaced whichever one reached its first step. + try: + from backend.apps.agents.core.ws_manager import ws_manager as _wsm_start + await _wsm_start.broadcast_global("workflow:run", { + "workflow_id": wf.id, + "run": run.model_dump(mode="json"), + }) + except Exception: + pass + session = None try: steps = [s for s in wf.steps if s.enabled and s.text and s.text.strip()] diff --git a/backend/apps/workflows/scheduler.py b/backend/apps/workflows/scheduler.py index 6fd0386e..650b40f1 100644 --- a/backend/apps/workflows/scheduler.py +++ b/backend/apps/workflows/scheduler.py @@ -362,6 +362,14 @@ def _mark_stuck_runs_failed() -> None: error="OpenSwarm closed before this run finished.", finished_at=now, ) + # The run row is fixed, but the workflow still summarizes this + # dead run as 'running' (that's what the detail header reads), so + # heal the summary too when this was the latest run. + if wf.last_run_id == r.id and wf.last_run_status == "running": + executor._persist_run_fields(wf, { + "last_run_status": "failure", + "last_run_at": now, + }) def record_skipped(wf: Workflow, scheduled_for: datetime, error: str) -> WorkflowRun: diff --git a/frontend/src/shared/state/workflowsSlice.ts b/frontend/src/shared/state/workflowsSlice.ts index 82c954df..f24e4a4b 100644 --- a/frontend/src/shared/state/workflowsSlice.ts +++ b/frontend/src/shared/state/workflowsSlice.ts @@ -230,6 +230,17 @@ function mergeRunIntoState(state: State, r: WorkflowRun) { wf.last_run_status = r.status === 'skipped' ? wf.last_run_status : (r.status as Workflow['last_run_status']); wf.last_run_id = r.id; } + // Keep the live "Ongoing runs" list in sync off the WS stream: a run that's no + // longer running drops out of `active`, a freshly-running one joins. Without + // this, `active` only refreshed on the one-shot mount fetch, so finished runs + // lingered as "Working…" while the monitor already showed them done. + const activeIdx = state.active.findIndex((a) => a.run_id === r.id); + if (r.status === 'running') { + const entry: ActiveRun = { workflow_id: r.workflow_id, run_id: r.id, title: wf?.title || '', started_at: r.started_at }; + if (activeIdx >= 0) state.active[activeIdx] = entry; else state.active.unshift(entry); + } else if (activeIdx >= 0) { + state.active.splice(activeIdx, 1); + } // Auto-flip the card view on run state transitions so the user sees // Running while it streams, Completed on success, Failed on failure. // Only nudge from views that the user hasn't actively navigated away