diff --git a/src/wf_api/runs.py b/src/wf_api/runs.py index 3d9936ee..f6eb67fc 100644 --- a/src/wf_api/runs.py +++ b/src/wf_api/runs.py @@ -439,6 +439,34 @@ class WorkflowRunApi: max_steps=max_steps, steps_executed=0, ) + if ( + record.status is StoredRunStatus.FAILED + and record.latest_checkpoint_id is None + ): + # Recovery can fail an admitted execution closed before any + # stopped checkpoint exists: the outcome is deliberately unknown, + # but the durable failure decision and its diagnostics are still + # actionable. Do not route this shape through checkpoint loading + # or fabricate trace/output/state from the failed decision. + try: + max_steps = store.get_admission(run_id).max_steps + except KeyError: + max_steps = None + failure_error = ( + record.diagnostics[-1].message if record.diagnostics else None + ) + return _run_payload( + deployment=record.environment.deployment, + artifact=record.environment.root_artifact, + status=record.status.value, + run_id=record.id, + resume_readiness=record.resume_readiness.value, + error=failure_error, + diagnostics=record.diagnostics, + max_steps=max_steps, + trace_count=0, + steps_executed=0, + ) record, run = load_stored_run(store, run_id) environment = record.environment return _run_payload( diff --git a/tests/wf_server/test_scheduler_integration.py b/tests/wf_server/test_scheduler_integration.py index 1a1fc7c5..fc84845c 100644 --- a/tests/wf_server/test_scheduler_integration.py +++ b/tests/wf_server/test_scheduler_integration.py @@ -761,6 +761,11 @@ async def test_graceful_shutdown_cancel_leaves_executing_for_recovery( and "external effects may already" in item.message for item in record.diagnostics ) + inspected = await server_b.api.inspect_run(run_id=run_id) + assert inspected["status"] == "failed" + assert inspected["resume_readiness"] == "not_applicable" + assert inspected["error"] is not None + assert "external effects may already" in inspected["error"] assert len(_entries(root, "hanging", "failed")) == 1 finally: _gate_open.set()