From 98d69d874cac4ecc93014434229ffb1e2c3991d2 Mon Sep 17 00:00:00 2001 From: Peter Wielander Date: Sat, 2 May 2026 15:44:55 +0900 Subject: [PATCH] [world-local] Reject run_failed on non-existent run Aligns world-local with world-postgres and world-vercel, which both throw WorkflowRunNotFoundError when a run_failed event is recorded against a run that was never created. world-local previously persisted the event silently with no run record. Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/world-local-run-failed-not-found.md | 5 +++++ packages/world-local/src/storage.test.ts | 8 ++++++++ packages/world-local/src/storage/events-storage.ts | 9 +++++++++ 3 files changed, 22 insertions(+) create mode 100644 .changeset/world-local-run-failed-not-found.md diff --git a/.changeset/world-local-run-failed-not-found.md b/.changeset/world-local-run-failed-not-found.md new file mode 100644 index 0000000000..66bdb5cfb2 --- /dev/null +++ b/.changeset/world-local-run-failed-not-found.md @@ -0,0 +1,5 @@ +--- +"@workflow/world-local": patch +--- + +Throw `WorkflowRunNotFoundError` when `run_failed` is recorded against a run that doesn't exist, matching the behaviour of `world-postgres` and `world-vercel`. diff --git a/packages/world-local/src/storage.test.ts b/packages/world-local/src/storage.test.ts index be08e28a2c..761a5dcb76 100644 --- a/packages/world-local/src/storage.test.ts +++ b/packages/world-local/src/storage.test.ts @@ -276,6 +276,14 @@ describe('Storage', () => { expect(updated.error?.message).toBe('Something went wrong'); expect(updated.completedAt).toBeInstanceOf(Date); }); + + it('should reject run_failed on non-existent run', async () => { + await expect( + updateRun(storage, 'wrun_nonexistent', 'run_failed', { + error: 'Something went wrong', + }) + ).rejects.toMatchObject({ name: 'WorkflowRunNotFoundError' }); + }); }); describe('list', () => { diff --git a/packages/world-local/src/storage/events-storage.ts b/packages/world-local/src/storage/events-storage.ts index fd6060f451..5af903696a 100644 --- a/packages/world-local/src/storage/events-storage.ts +++ b/packages/world-local/src/storage/events-storage.ts @@ -6,6 +6,7 @@ import { RunExpiredError, RunNotSupportedError, TooEarlyError, + WorkflowRunNotFoundError, WorkflowWorldError, } from '@workflow/errors'; import type { @@ -226,6 +227,14 @@ export function createEventsStorage( } } + // run_failed on a non-existent run is rejected to match the + // postgres and vercel worlds, which both surface this as a + // WorkflowRunNotFoundError rather than silently persisting an + // event for a run that was never created. + if (data.eventType === 'run_failed' && !currentRun) { + throw new WorkflowRunNotFoundError(effectiveRunId); + } + // ============================================================ // VERSION COMPATIBILITY: Check run spec version // ============================================================