fix: preserve and display workflow retry details (#38854)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Blackoutta
2026-07-15 05:58:37 +00:00
committed by GitHub
co-authored by autofix-ci[bot]
parent 7dd33929c7
commit 7dc3126ff4
31 changed files with 757 additions and 75 deletions
+44 -1
View File
@@ -15,6 +15,7 @@ from datetime import datetime
from typing import Any, Union, override
from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, WorkflowAppGenerateEntity
from core.app.workflow.retry_history import RETRY_HISTORY_PROCESS_DATA_KEY, WorkflowNodeRetryAttempt
from core.helper.trace_id_helper import ParentTraceContext
from core.ops.entities.trace_entity import TraceTaskName
from core.ops.ops_trace_manager import TraceQueueManager, TraceTask
@@ -257,6 +258,7 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
domain_execution = self._get_node_execution(event.id)
domain_execution.status = WorkflowNodeExecutionStatus.RETRY
domain_execution.error = event.error
self._append_retry_history(domain_execution, event)
self._workflow_node_execution_repository.save(domain_execution)
self._workflow_node_execution_repository.save_execution_data(domain_execution)
_inspector_publish_node_changed(
@@ -356,6 +358,46 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
self._node_sequence += 1
return self._node_sequence
def _append_retry_history(self, execution: WorkflowNodeExecution, event: NodeRunRetryEvent) -> None:
"""Append a validated full attempt before repository truncation or offload."""
finished_at = naive_utc_now()
process_data = dict(execution.process_data or {})
raw_history = process_data.get(RETRY_HISTORY_PROCESS_DATA_KEY)
history = list(raw_history) if isinstance(raw_history, list) else []
projected_outputs = project_node_outputs_for_workflow_run(
node_type=execution.node_type,
inputs=event.node_run_result.inputs,
outputs=event.node_run_result.outputs,
)
attempt = WorkflowNodeRetryAttempt(
retry_index=event.retry_index,
inputs=event.node_run_result.inputs,
process_data=event.node_run_result.process_data,
outputs=projected_outputs,
error=event.node_run_result.error or event.error,
elapsed_time=max((finished_at - event.start_at).total_seconds(), 0.0),
execution_metadata={key.value: value for key, value in event.node_run_result.metadata.items()},
created_at=int(event.start_at.timestamp()),
finished_at=int(finished_at.timestamp()),
)
history.append(attempt.model_dump(mode="json"))
process_data[RETRY_HISTORY_PROCESS_DATA_KEY] = history
execution.process_data = process_data
@staticmethod
def _merge_retry_history(
existing_process_data: Mapping[str, Any] | None,
next_process_data: Mapping[str, Any] | None,
) -> Mapping[str, Any] | None:
"""Keep internal retry history while replacing node-specific Process Data."""
raw_history = (existing_process_data or {}).get(RETRY_HISTORY_PROCESS_DATA_KEY)
if not isinstance(raw_history, list) or not raw_history:
return next_process_data
merged_process_data = dict(next_process_data or {})
merged_process_data[RETRY_HISTORY_PROCESS_DATA_KEY] = raw_history
return merged_process_data
def _populate_completion_statistics(self, execution: WorkflowExecution, *, update_finished: bool = True) -> None:
if update_finished:
execution.finished_at = naive_utc_now()
@@ -391,9 +433,10 @@ class WorkflowPersistenceLayer(GraphEngineLayer):
inputs=node_result.inputs,
outputs=node_result.outputs,
)
process_data = self._merge_retry_history(domain_execution.process_data, node_result.process_data)
domain_execution.update_from_mapping(
inputs=node_result.inputs,
process_data=node_result.process_data,
process_data=process_data,
outputs=projected_outputs,
metadata=node_result.metadata,
)
+24
View File
@@ -0,0 +1,24 @@
"""Validated internal payloads for persisted workflow node retry history."""
from collections.abc import Mapping
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
RETRY_HISTORY_PROCESS_DATA_KEY = "__dify_retry_history"
class WorkflowNodeRetryAttempt(BaseModel):
"""Complete data captured for one failed node attempt before a retry."""
retry_index: int = Field(gt=0)
inputs: Mapping[str, Any]
process_data: Mapping[str, Any]
outputs: Mapping[str, Any]
error: str
elapsed_time: float = Field(ge=0)
execution_metadata: Mapping[str, Any]
created_at: int
finished_at: int
model_config = ConfigDict(extra="forbid")
+1 -1
View File
@@ -200,7 +200,7 @@ def make_request(method: str, url: str, max_retries: int = SSRF_DEFAULT_MAX_RETR
f"The URL may point to a private or local network address. "
)
if response.status_code not in STATUS_FORCELIST:
if response.status_code not in STATUS_FORCELIST or max_retries == 0:
return response
else:
logger.warning(