handleRequest("NestJS")}
+ onClick={() => handleRequest('NestJS')}
>
handleRequest("TanStack")}
+ onClick={() => handleRequest('TanStack')}
>
handleRequest("Astro")}
+ onClick={() => handleRequest('Astro')}
>
diff --git a/packages/core/e2e/e2e.test.ts b/packages/core/e2e/e2e.test.ts
index 92336b08e..a1a701d61 100644
--- a/packages/core/e2e/e2e.test.ts
+++ b/packages/core/e2e/e2e.test.ts
@@ -776,4 +776,27 @@ describe('e2e', () => {
expect(returnValue).toBe('Result: 21');
}
);
+
+ test(
+ 'stepExhaustsRetriesWorkflow - workflow fails when step exhausts max retries',
+ { timeout: 120_000 },
+ async () => {
+ // This workflow has a step that always throws a regular error.
+ // After exhausting max retries (default 3), the error should bubble up
+ // to the workflow function. Since the workflow doesn't handle it,
+ // the workflow run should enter a failed state.
+ const run = await triggerWorkflow('stepExhaustsRetriesWorkflow', []);
+ const returnValue = await getWorkflowReturnValue(run.runId);
+
+ // The workflow should fail with WorkflowRunFailedError
+ expect(returnValue).toHaveProperty('name');
+ expect(returnValue.name).toBe('WorkflowRunFailedError');
+
+ // Verify the run is in failed status
+ const { json: runData } = await cliInspectJson(`runs ${run.runId}`);
+ expect(runData.status).toBe('failed');
+ expect(runData.error).toBeDefined();
+ expect(runData.error.message).toContain('failed after max retries');
+ }
+ );
});
diff --git a/workbench/example/workflows/99_e2e.ts b/workbench/example/workflows/99_e2e.ts
index df5e0b22c..3b2f5e208 100644
--- a/workbench/example/workflows/99_e2e.ts
+++ b/workbench/example/workflows/99_e2e.ts
@@ -559,3 +559,21 @@ export async function closureVariableWorkflow(baseValue: number) {
const output = await calculate();
return output;
}
+
+//////////////////////////////////////////////////////////
+
+// Step that always throws a regular error (not FatalError)
+// This will exhaust max retries and the error should bubble up to the workflow
+async function stepThatAlwaysFailsWithRegularError() {
+ 'use step';
+ throw new Error('This step always fails with a regular error');
+}
+
+// Workflow that calls a step that always fails
+// The step will exhaust max retries and the error should bubble up,
+// causing the workflow run to enter a failed state
+export async function stepExhaustsRetriesWorkflow() {
+ 'use workflow';
+ await stepThatAlwaysFailsWithRegularError();
+ return 'never reached';
+}