* Fix run_failed event schema validation failure in lazy ref mode
The EventResultWireSchema used the strict WorkflowRunSchema (discriminated
union) which requires error: StructuredErrorSchema for status 'failed'. When
run_failed events use remoteRefBehavior 'lazy', the server returns the error
field as a RemoteRef or undefined, causing Zod validation to fail.
Switch to WorkflowRunWireBaseSchema which accepts error as string, structured
object, or undefined, and apply deserializeError() to normalize the run entity
before returning it to consumers.
Closes#1180
* Use strict WorkflowRunSchema for resolve mode, loose wire schema for lazy mode
Split EventResultWireSchema into two variants based on remoteRefBehavior:
- EventResultResolveWireSchema uses WorkflowRunSchema (discriminated union)
for events where the client reads result.run (run_created, run_started,
step_started), preserving type safety.
- EventResultLazyWireSchema uses WorkflowRunWireBaseSchema for events where
the client discards the response, allowing error to be a string or
undefined in lazy ref mode.
* Update changeset to reflect resolve/lazy schema split
* Simplify changeset description
* Add Fluid compute warning callout to Vercel World docs
Follow-up from #1216 review feedback to include the
FluidComputeCallout on the Vercel World page.
* Register FluidComputeCallout in worlds/[id] page
The /worlds/vercel route uses a separate page component from
/docs/... and was missing the FluidComputeCallout in its MDX
component map, causing a 500 error.
* fix(builders): enable directive discovery in dot-prefixed files and directories
The glob-based file scanning in getInputFiles() was using tinyglobby's
default behavior which skips dot-prefixed files/directories. This prevented
discovering files with 'use step' / 'use workflow' directives inside paths
like .config/step.ts or .hidden-workflow.ts.
Switch to relative glob patterns with per-directory cwd and dot: true to
ensure dot-files are scanned while still respecting the explicit ignore
list (.git, .next, .vercel, etc.).
* test: add E2E tests for dot-directory directive discovery
Add workbench fixtures and E2E tests verifying that 'use step' / 'use
workflow' directives inside dot-prefixed directories (.well-known/agent/)
are correctly discovered, included in manifests, and executed at runtime.
* fix: normalize paths in getInputFiles tests for Windows compatibility
tinyglobby returns forward-slash paths even on Windows, while Node's
path.join() uses backslashes. Normalize both sides to forward slashes
before comparison.
* refactor: use dirname() instead of join(.., '..') in test helper
Cleaner and more idiomatic way to get the parent directory.
* fix: add .nuxt and other build tool dot-directories to ignore list
With dot: true enabled, framework build output directories like .nuxt/
are now traversed and their generated files picked up as input files.
This caused a circular dependency in the nuxt builder where generated
.nuxt/workflow/steps.mjs was being re-discovered as an input file.
Add .nuxt, .turbo, .cache, .yarn, and .pnpm-store to the ignore list,
matching the directories already ignored by the eager builder's watcher.
Move the Instance and Class reducers before the Error reducer in
getCommonReducers() so that Error subclasses implementing
WORKFLOW_SERIALIZE are serialized using custom class serialization
instead of the generic Error reducer. devalue uses first-match-wins,
so the previous ordering caused custom Error subclasses to lose their
custom fields (only name/message/stack were preserved).
* feat(core): add hook.dispose() method to release hook tokens early
Add a `dispose()` method to the Hook interface that allows workflows to
explicitly release hook tokens for reuse by other workflows while the
current workflow is still running. This enables handoff patterns where
one workflow can transfer a hook token to another workflow.
- Add `dispose()` method to Hook interface in create-hook.ts
- Implement dispose functionality in workflow/hook.ts
- Add HookDisposedInvocationQueueItem to global.ts
- Handle hook_disposed events in suspension-handler.ts
- Update documentation in hooks.mdx and create-hook.mdx
- Add e2e test for hook token reuse after explicit disposal
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* feat(core): implement TypeScript Disposable spec for hooks
Add Symbol.dispose to Hook interface to support the TC39 Explicit Resource
Management proposal. This allows hooks to be used with the `using` keyword
for automatic disposal when exiting scope.
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* docs: prefer `using` keyword for hooks and webhooks
Update all documentation and e2e tests to use the `using` keyword as the
recommended approach for creating hooks and webhooks. This leverages the
TC39 Explicit Resource Management proposal for automatic disposal.
- Update e2e tests to use `using` syntax
- Update foundational hooks guide to recommend `using`
- Update create-hook API reference with `using` examples
- Update create-webhook API reference with `using` examples
- Update example workflow to use `using`
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* chore: simplify `using` examples in docs and e2e tests
Remove unnecessary block scopes and excessive comments about automatic
disposal. Block scopes are only used when early disposal is relevant
(like in the handoff test).
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* docs: add brief explanation of `using` in first examples
Add a one-liner explaining the `using` keyword in the intro examples
of the API reference docs, so new users understand the syntax.
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* fix: address PR review comments
- Restore code highlights (`[!code highlight]`) that were unintentionally
removed from pre-existing doc examples
- Move `using` explanation from prose to inline code comment in intro
examples
- Add `{/* @skip-typecheck */}` to incomplete manual dispose() snippet
- Add 409 (conflict/duplicate) error handling for hook_disposed events
in suspension handler to handle workflow re-invocation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: polyfill Symbol.dispose in workflow VM context
The workflow VM sandbox doesn't have Symbol.dispose/Symbol.asyncDispose
available, causing `using` keyword to fail with "Symbol.dispose is not
defined" at runtime. Add polyfill in the VM context creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use VM's Symbol.dispose for hook disposable support
The workflow VM sandbox has its own Symbol object with a polyfilled
Symbol.dispose. The hook object was using the host's Symbol.dispose,
which is a different symbol instance. The SWC-compiled `using` keyword
looks up the VM's Symbol.dispose on the object, causing "Object not
disposable" errors.
Fix by setting Symbol.dispose on the hook object dynamically using the
VM's globalThis.Symbol.dispose from the orchestrator context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert webhookWorkflow to create all webhooks upfront
The webhookWorkflow e2e test creates 3 webhooks that must all exist
before the test sends HTTP requests. Using `using` with sequential
creation meant only the first webhook existed at the first suspension
point. Revert to `const` since all webhooks need to be created upfront.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: eliminate hook_disposed queue item, use flag pattern for disposal
Instead of adding a separate HookDisposedInvocationQueueItem to the
queue on dispose, keep the HookInvocationQueueItem throughout the
hook lifecycle and track state with flags (hasCreatedEvent, disposed).
A closure variable (hasDisposedEvent) makes disposeHook() a pure no-op
on replay, avoiding redundant server calls and 409 errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: delete hook from invocations queue on hook_disposed terminal event
Match the pattern used by steps (step_completed/step_failed) and waits
(wait_completed) where the queue item is removed on the terminal event.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: verify dispose() is safe when called twice after replay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add comprehensive edge case tests for hook disposal
- WorkflowSuspension counts disposed hooks separately from active hooks
- Dispose after hook_created replay produces correct suspension
- Dispose before first suspension (needs both create + dispose)
- Multiple hooks where only one is disposed
- Dispose on a conflicted hook is safe (no crash)
- Symbol.dispose calls disposeHook correctly (using keyword pattern)
- Iterator break without dispose keeps hook alive in queue
- Await after dispose on first invocation triggers suspension
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unsafe cast by adding Symbol.dispose to hook object literal
Add [Symbol.dispose] directly to the hook object so it satisfies the
Hook<T> type without `as unknown as`. The VM's Symbol.dispose is still
added separately when it differs from the host's.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address remaining Copilot review comments
- Rename test to match new behavior (hooks stay in queue, not removed)
- Use neutral "processed" verb in WorkflowSuspension message when
mixed item types are present
- Remove extends Disposable from Hook interface to avoid requiring
lib.esnext.disposable in downstream consumers (explicit
[Symbol.dispose]() method is still declared on the interface)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: drain pending promises in disposeHook to prevent orphaned awaits
When dispose() is called while a promise is pending (e.g., iterator
suspended on yield await this, or direct await hook after dispose),
the promise would hang forever since the event consumer will never
deliver another hook_received. Now disposeHook() clears the promises
array and triggers a WorkflowSuspension so the runtime processes the
disposal cleanly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove workflow from changeset, keep only @workflow/core
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(core): warn on uncommitted operations when workflow completes
Log a warning when a workflow run completes or fails with uncommitted
operations still in the invocations queue (unawaited steps, hooks, or
sleeps). This helps users diagnose forgotten `await` calls.
Hook disposals are excluded from the warning since the backend
auto-disposes all hooks when a run reaches a terminal state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add tests for pending queue warning on workflow completion
Test that:
- Unawaited step triggers warning on successful completion
- Unawaited step triggers warning on workflow failure
- hook_disposed items are excluded from warning
- Clean workflow completion produces no warning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update warnPendingQueueItems filter for new hook flag pattern
Filter out hooks with hasCreatedEvent && !disposed (alive hooks) instead
of the removed hook_disposed queue item type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review comments
- Remove workflow from changeset (transitive via @workflow/core)
- Add default case to switch for exhaustiveness
- Use correlationId instead of token in warning message to avoid leaking
user-provided tokens into logs
- Remove step events from warning tests so unawaited steps stay pending
in the queue (step_completed would clear the item during replay)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review comments on pending queue warnings
- Filter out disposed hooks from warning (not just created hooks)
- Show hook token instead of correlationId in warning (user-facing)
- Rename test to "should not warn when hooks are properly disposed"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(core): add hook.dispose() method to release hook tokens early
Add a `dispose()` method to the Hook interface that allows workflows to
explicitly release hook tokens for reuse by other workflows while the
current workflow is still running. This enables handoff patterns where
one workflow can transfer a hook token to another workflow.
- Add `dispose()` method to Hook interface in create-hook.ts
- Implement dispose functionality in workflow/hook.ts
- Add HookDisposedInvocationQueueItem to global.ts
- Handle hook_disposed events in suspension-handler.ts
- Update documentation in hooks.mdx and create-hook.mdx
- Add e2e test for hook token reuse after explicit disposal
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* feat(core): implement TypeScript Disposable spec for hooks
Add Symbol.dispose to Hook interface to support the TC39 Explicit Resource
Management proposal. This allows hooks to be used with the `using` keyword
for automatic disposal when exiting scope.
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* docs: prefer `using` keyword for hooks and webhooks
Update all documentation and e2e tests to use the `using` keyword as the
recommended approach for creating hooks and webhooks. This leverages the
TC39 Explicit Resource Management proposal for automatic disposal.
- Update e2e tests to use `using` syntax
- Update foundational hooks guide to recommend `using`
- Update create-hook API reference with `using` examples
- Update create-webhook API reference with `using` examples
- Update example workflow to use `using`
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* chore: simplify `using` examples in docs and e2e tests
Remove unnecessary block scopes and excessive comments about automatic
disposal. Block scopes are only used when early disposal is relevant
(like in the handoff test).
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* docs: add brief explanation of `using` in first examples
Add a one-liner explaining the `using` keyword in the intro examples
of the API reference docs, so new users understand the syntax.
https://claude.ai/code/session_01AkvrXduyFTbtV2joTPHrDH
* fix: address PR review comments
- Restore code highlights (`[!code highlight]`) that were unintentionally
removed from pre-existing doc examples
- Move `using` explanation from prose to inline code comment in intro
examples
- Add `{/* @skip-typecheck */}` to incomplete manual dispose() snippet
- Add 409 (conflict/duplicate) error handling for hook_disposed events
in suspension handler to handle workflow re-invocation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: polyfill Symbol.dispose in workflow VM context
The workflow VM sandbox doesn't have Symbol.dispose/Symbol.asyncDispose
available, causing `using` keyword to fail with "Symbol.dispose is not
defined" at runtime. Add polyfill in the VM context creation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use VM's Symbol.dispose for hook disposable support
The workflow VM sandbox has its own Symbol object with a polyfilled
Symbol.dispose. The hook object was using the host's Symbol.dispose,
which is a different symbol instance. The SWC-compiled `using` keyword
looks up the VM's Symbol.dispose on the object, causing "Object not
disposable" errors.
Fix by setting Symbol.dispose on the hook object dynamically using the
VM's globalThis.Symbol.dispose from the orchestrator context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: revert webhookWorkflow to create all webhooks upfront
The webhookWorkflow e2e test creates 3 webhooks that must all exist
before the test sends HTTP requests. Using `using` with sequential
creation meant only the first webhook existed at the first suspension
point. Revert to `const` since all webhooks need to be created upfront.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: eliminate hook_disposed queue item, use flag pattern for disposal
Instead of adding a separate HookDisposedInvocationQueueItem to the
queue on dispose, keep the HookInvocationQueueItem throughout the
hook lifecycle and track state with flags (hasCreatedEvent, disposed).
A closure variable (hasDisposedEvent) makes disposeHook() a pure no-op
on replay, avoiding redundant server calls and 409 errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: delete hook from invocations queue on hook_disposed terminal event
Match the pattern used by steps (step_completed/step_failed) and waits
(wait_completed) where the queue item is removed on the terminal event.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: verify dispose() is safe when called twice after replay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add comprehensive edge case tests for hook disposal
- WorkflowSuspension counts disposed hooks separately from active hooks
- Dispose after hook_created replay produces correct suspension
- Dispose before first suspension (needs both create + dispose)
- Multiple hooks where only one is disposed
- Dispose on a conflicted hook is safe (no crash)
- Symbol.dispose calls disposeHook correctly (using keyword pattern)
- Iterator break without dispose keeps hook alive in queue
- Await after dispose on first invocation triggers suspension
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unsafe cast by adding Symbol.dispose to hook object literal
Add [Symbol.dispose] directly to the hook object so it satisfies the
Hook<T> type without `as unknown as`. The VM's Symbol.dispose is still
added separately when it differs from the host's.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address remaining Copilot review comments
- Rename test to match new behavior (hooks stay in queue, not removed)
- Use neutral "processed" verb in WorkflowSuspension message when
mixed item types are present
- Remove extends Disposable from Hook interface to avoid requiring
lib.esnext.disposable in downstream consumers (explicit
[Symbol.dispose]() method is still declared on the interface)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: drain pending promises in disposeHook to prevent orphaned awaits
When dispose() is called while a promise is pending (e.g., iterator
suspended on yield await this, or direct await hook after dispose),
the promise would hang forever since the event consumer will never
deliver another hook_received. Now disposeHook() clears the promises
array and triggers a WorkflowSuspension so the runtime processes the
disposal cleanly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove workflow from changeset, keep only @workflow/core
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* [docs] Split the Worlds page into sections
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* Wording
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* Update text
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* remove benchmark numbers
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
---------
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
* Fix og route handling in i18n middleware
* Add docs CI smoke test for OG and sitemap
* Run docs OG checks against Vercel preview
* Refactor docs CI checks and rename smoke test
* Use docs Vercel token for preview checks
* Expand docs OG smoke coverage
* Check docs HTML OG metadata
* [@workflow/world-vercel] Handle null key response from run-key API endpoint
The API endpoint now returns { key: null } instead of a 404 when a
deployment has no encryption key. Update fetchRunKey to accept the
nullable response and return undefined, signaling that encryption
is disabled for that workflow run.
* [@workflow/world-vercel] Add changeset for null run-key handling
* Address review feedback: improve error message and fix test mock cleanup
- Include Zod error details in the validation error message for better debugging
- Move fetch mock restoration to afterEach to prevent mock leakage on test failure
* fix: use types.isNativeError() for cross-VM Error serialization
FatalError was not properly serialized when passed from workflow code into a step function because the Error reducer checked `value instanceof global.Error` where `global` is the VM's globalThis. Errors created in the host context (like FatalError from @workflow/errors) have a different Error prototype than the VM context, so the instanceof check returned false and the error was silently dropped.
Replaced with `types.isNativeError()` from `node:util` which uses V8's internal type tag and works across VM context boundaries.
* ci: don't cancel in-progress CI runs on main branch
FatalError was not properly serialized when passed from workflow code into a step function because the Error reducer checked `value instanceof global.Error` where `global` is the VM's globalThis. Errors created in the host context (like FatalError from @workflow/errors) have a different Error prototype than the VM context, so the instanceof check returned false and the error was silently dropped.
Replaced with `types.isNativeError()` from `node:util` which uses V8's internal type tag and works across VM context boundaries.
* Bring back sitemap
* Bring back sitemap
* Add pre commit hooks for sitemap
* Add pre commit hooks for sitemap
* Fix failing queue test
* Revert queue test failure
* Add unit tests
* Fix sleep state in side panel
* Side panel improvements
* Side panel improvements
* Side panel improvements
* Side panel improvements
* Side panel improvements