From 000e8bd12baa340a5f9e1938b9d0ff75957da4d2 Mon Sep 17 00:00:00 2001 From: zhsama Date: Thu, 6 Nov 2025 14:05:45 +0800 Subject: [PATCH] fix: improve error handling in useOneStepRun and useWorkflowRun to provide structured error messages --- .../workflow-app/hooks/use-workflow-run.ts | 29 ++-- .../nodes/_base/hooks/use-one-step-run.ts | 135 +++++++++--------- 2 files changed, 86 insertions(+), 78 deletions(-) diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index fb4ba8a652..3ab1c522e7 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -596,19 +596,22 @@ export const useWorkflowRun = () => { catch (error) { if (controller.signal.aborted) return - console.error(`handleRun: ${debugLabel.toLowerCase()} debug polling error`, error) - Toast.notify({ type: 'error', message: `${debugLabel} debug request failed` }) - clearAbortController() - setWorkflowRunningData({ - result: { - status: WorkflowRunningStatus.Failed, - error: `${debugLabel} debug request failed`, - inputs_truncated: false, - process_data_truncated: false, - outputs_truncated: false, - }, - tracing: [], - }) + if (error instanceof Response) { + const data = await error.clone().json() as Record + const { error: respError } = data || {} + Toast.notify({ type: 'error', message: respError }) + clearAbortController() + setWorkflowRunningData({ + result: { + status: WorkflowRunningStatus.Failed, + error: respError, + inputs_truncated: false, + process_data_truncated: false, + outputs_truncated: false, + }, + tracing: [], + }) + } clearListeningState() } } diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts index e1d6af449c..58f093403c 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts @@ -85,7 +85,12 @@ const checkValidFns: Record = { [BlockEnum.Iteration]: checkIterationValid, [BlockEnum.DocExtractor]: checkDocumentExtractorValid, [BlockEnum.Loop]: checkLoopValid, -} as any +} + +type RequestError = { + message: string + status: string +} export type Params = { id: string @@ -510,7 +515,6 @@ const useOneStepRun = ({ if (controller.signal.aborted) return null - console.error('handleRun: webhook debug polling error', error) Toast.notify({ type: 'error', message: 'Webhook debug request failed' }) cancelWebhookSingleRun() if (error instanceof Error) @@ -535,78 +539,79 @@ const useOneStepRun = ({ const controller = new AbortController() pluginSingleRunAbortRef.current = controller - try { - const response: any = await post(urlPath, { - body: JSON.stringify({}), - signal: controller.signal, - }) - - if (!pluginSingleRunActiveRef.current || token !== pluginSingleRunTokenRef.current) - return null - - if (!response) { - const message = response?.message || 'Plugin debug failed' - Toast.notify({ type: 'error', message }) - cancelPluginSingleRun() - throw new Error(message) + let requestError: RequestError | undefined + const response: any = await post(urlPath, { + body: JSON.stringify({}), + signal: controller.signal, + }).catch(async (error: Response) => { + const data = await error.clone().json() as Record + const { error: respError, status } = data || {} + requestError = { + message: respError, + status, } + return null + }).finally(() => { + pluginSingleRunAbortRef.current = null + }) - if (response?.status === 'waiting') { - const delay = Number(response.retry_in) || 2000 - pluginSingleRunAbortRef.current = null - if (!pluginSingleRunActiveRef.current || token !== pluginSingleRunTokenRef.current) - return null + if (!pluginSingleRunActiveRef.current || token !== pluginSingleRunTokenRef.current) + return null - await new Promise((resolve) => { - const timeoutId = window.setTimeout(resolve, delay) - pluginSingleRunTimeoutRef.current = timeoutId - pluginSingleRunDelayResolveRef.current = resolve - controller.signal.addEventListener('abort', () => { - window.clearTimeout(timeoutId) - resolve() - }, { once: true }) - }) - - pluginSingleRunTimeoutRef.current = undefined - pluginSingleRunDelayResolveRef.current = null - continue - } - - if (response?.status === 'error') { - const message = response.message || 'Plugin debug failed' - Toast.notify({ type: 'error', message }) - cancelPluginSingleRun() - throw new Error(message) - } - - handleNodeDataUpdate({ - id, - data: { - ...data, - _isSingleRun: false, - _singleRunningStatus: NodeRunningStatus.Listening, - }, - }) - - cancelPluginSingleRun() - return response - } - catch (error) { - if (controller.signal.aborted && (!pluginSingleRunActiveRef.current || token !== pluginSingleRunTokenRef.current)) - return null + if (requestError) { if (controller.signal.aborted) return null - console.error('handleRun: plugin debug polling error', error) - Toast.notify({ type: 'error', message: 'Plugin debug request failed' }) + Toast.notify({ type: 'error', message: requestError.message }) cancelPluginSingleRun() - if (error instanceof Error) - throw error - throw new Error(String(error)) + throw requestError } - finally { - pluginSingleRunAbortRef.current = null + + if (!response) { + const message = 'Plugin debug failed' + Toast.notify({ type: 'error', message }) + cancelPluginSingleRun() + throw new Error(message) } + + if (response?.status === 'waiting') { + const delay = Number(response.retry_in) || 2000 + if (!pluginSingleRunActiveRef.current || token !== pluginSingleRunTokenRef.current) + return null + + await new Promise((resolve) => { + const timeoutId = window.setTimeout(resolve, delay) + pluginSingleRunTimeoutRef.current = timeoutId + pluginSingleRunDelayResolveRef.current = resolve + controller.signal.addEventListener('abort', () => { + window.clearTimeout(timeoutId) + resolve() + }, { once: true }) + }) + + pluginSingleRunTimeoutRef.current = undefined + pluginSingleRunDelayResolveRef.current = null + continue + } + + if (response?.status === 'error') { + const message = response.message || 'Plugin debug failed' + Toast.notify({ type: 'error', message }) + cancelPluginSingleRun() + throw new Error(message) + } + + handleNodeDataUpdate({ + id, + data: { + ...data, + _isSingleRun: false, + _singleRunningStatus: NodeRunningStatus.Listening, + }, + }) + + cancelPluginSingleRun() + return response } return null