* feat: export semantic error types and add API reference documentation
Add missing error exports (HookNotFoundError, EntityConflictError,
RunExpiredError, TooEarlyError, ThrottleError, RunNotSupportedError,
WorkflowWorldError) to workflow/internal/errors. Create new error
classes for world-level semantics. Tighten TSDoc comments on all
error classes. Add API reference docs for all error types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use @setup declarations, workflow/errors import, and errors/ doc section
- Replace @skip-typecheck with proper `declare` + `// @setup` lines
so code samples are typechecked but setup lines hidden from readers
- Add `workflow/errors` export to package.json (public API, replaces
`workflow/internal/errors` in docs)
- Add `workflow/errors` path mapping in docs-typecheck type-checker
- Add HookConflictError to re-export list
- Move all error docs under api-reference/workflow/errors/ subdirectory
- Update all internal cross-references and links
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: move error docs to top-level workflow-errors section
- Move semantic error docs to api-reference/workflow-errors/ (matching
the workflow/errors import path, like workflow-api for workflow/api)
- Keep FatalError and RetryableError in api-reference/workflow/ since
they're imported from workflow, not workflow/errors
- Fix all cross-reference links
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update HTTP debug logger JSDoc to clarify scope
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: make TooEarlyError.retryAfter a number (seconds) matching WorkflowWorldError
TooEarlyError.retryAfter is now seconds (number) instead of a Date,
consistent with ThrottleError and WorkflowWorldError. The conversion
from seconds to Date is done at the consumer site (step-handler) rather
than at construction time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback on docs accuracy
- WorkflowWorldError docs: add status, code, url, retryAfter properties
to TSDoc; clarify that .is() only matches direct instances (not
subclasses); use instanceof in catch-all example
- TooEarlyError/ThrottleError docs: mark retryAfter as optional (?)
to match actual type definitions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: enforce max queue deliveries in handlers with graceful failure
Replace VQS maxDeliveries cap with handler-level enforcement. Handlers
now gracefully fail runs/steps after excessive queue redeliveries,
preventing "phantom stuck" runs.
- Add MAX_QUEUE_DELIVERIES constant (64) and enforce in both workflow
and step handlers with run_failed/step_failed events
- Remove maxDeliveries from VQS trigger configs (builders + sveltekit)
- Improve world-local queue: safety limit loop, structured logging
with runId/stepId, backoff delay on failures
- Add MAX_DELIVERIES_EXCEEDED error code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* address PR review comments
- Expand max-delivery comments explaining minimal-work approach
- Make workflow handler error message verbose (matching step handler)
- Fix comment: "consume the message silently"
- Reduce local queue safety limit from 1000 to 256
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* change MAX_QUEUE_DELIVERIES to 48 and use 5s linear backoff locally
VQS uses linear 5s backoff for attempts 1-32, then exponential capped
at 2h. At 48 attempts total elapsed time is ~20h, safely under the
24h message visibility limit. Local world now uses 5s linear backoff
to approximate VQS timing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Inline class serialization registration to fix 3rd-party package support
The SWC plugin previously generated:
import { registerSerializationClass } from "workflow/internal/class-serialization";
registerSerializationClass("class//...", ClassName);
This broke for 3rd-party packages (e.g. @vercel/sandbox) that define
serializable classes but don't depend on the 'workflow' package. The
bare 'workflow' specifier is unresolvable from within node_modules of
a package that doesn't list it as a dependency.
Now the plugin generates a self-contained IIFE that uses
Symbol.for('workflow-class-registry') on globalThis directly, with
zero module dependencies:
(function(__wf_cls, __wf_id) {
var __wf_sym = Symbol.for("workflow-class-registry"),
__wf_reg = globalThis[__wf_sym] || (globalThis[__wf_sym] = new Map());
__wf_reg.set(__wf_id, __wf_cls);
Object.defineProperty(__wf_cls, "classId", { ... });
})(ClassName, "class//...");
This is fully compatible with the existing deserialization side in
@workflow/core which reads from the same globalThis registry.
* Address review feedback: fix comment and update docstring
- Fix inaccurate IIFE comment in lib.rs: the second arg is the
generated class ID string, not the literal "classId"
- Update registerSerializationClass docstring to reflect that the
SWC plugin now inlines equivalent logic rather than importing it
* Add CommonJS require() support for class serialization detection in SWC plugin
The SWC compiler plugin now detects classes with custom serialization methods (`WORKFLOW_SERIALIZE` / `WORKFLOW_DESERIALIZE`) when symbols are obtained via CommonJS `require()` calls, in addition to the existing ESM import and `Symbol.for()` patterns.
This handles code that has been pre-compiled from ESM to CommonJS by tools like TypeScript (tsc), esbuild, or tsup, which transform:
```ts
import { WORKFLOW_SERIALIZE } from "@workflow/serde"
```
into either:
```ts
const serde_1 = require("@workflow/serde") // namespace require
const { WORKFLOW_SERIALIZE } = require(...) // destructured require
```
Both patterns are now recognized during the identifier collection phase, and classes using them are properly registered with registerSerializationClass().
* Address review: tighten is_require_call and extract track_serialization_bindings helper
- Tighten is_require_call to require exactly one string literal argument,
rejecting dynamic requires and multi-arg calls.
- Extract track_serialization_bindings helper to deduplicate the require
tracking logic between Decl::Var and ExportDecl(Decl::Var) branches.
* Add support for calling `start()` directly inside workflow functions
Enable `start()` to work in workflow context by routing through an
internal step (`__workflow_start`), reusing existing step infrastructure
with no new event types or server changes needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Address PR review feedback
- Use typeof check instead of truthiness for WORKFLOW_START symbol
- Validate start() options in workflow context (reject unsupported options like world)
- Set maxRetries=0 on __workflow_start step to prevent orphaned child runs
- Add unit tests for createStart factory (6 tests)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Make Run serializable in workflow context with step-backed methods
- Add Run serialization via __serializable marker + custom Run reducer/reviver
in the serialization module (avoids SWC plugin injecting class-serialization imports)
- Create WorkflowRun class factory (packages/core/src/workflow/run.ts) with
step-backed methods: cancel(), status, returnValue, workflowName, createdAt,
startedAt, completedAt, exists
- Register 8 built-in steps (__run_cancel, __run_status, etc.) in step-handler
- Update __workflow_start to return full Run object (serialized → WorkflowRun in VM)
- Update createStart to pass through step result directly
- Update docs to reflect full Run support in workflow context
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix start() in workflow VM by delegating from api-workflow stub
The workflow VM loads api-workflow.ts (via the "workflow" export condition)
which stubs all runtime functions. The start stub needs to check for the
injected WORKFLOW_START symbol and delegate to it, otherwise start() throws
"doesn't allow this runtime usage" in the workflow context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address PR review: fix stale WORKFLOW_SERIALIZE comments and register Run in host registry
- Update comments in step-handler.ts and start.ts to reference the actual
serialization mechanism (Run reducer with __serializable marker) instead
of the stale WORKFLOW_SERIALIZE reference
- Register Run class in the host's class registry from step-handler.ts so
the Run reviver can deserialize Run/WorkflowRun instances in step context
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add docs for recursive/repeating workflows and deploymentId: "latest"
- Document using start() for self-chaining workflows to avoid large event logs
- Add examples for batch processing and cron-like repeating patterns
- Document deploymentId: "latest" option with type safety warning
- Update skill file with same patterns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Return full Run object from startFromWorkflow e2e workflow
Update the e2e workflow to return the childRun object directly instead of
just childRun.runId, exercising Run serialization across the workflow boundary.
Update e2e test assertions to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add recursive fibonacci e2e test for start() in workflow
Demonstrates recursive workflow composition: fibonacciWorkflow starts
new instances of itself via start() + Promise.all to compute fib(6)=8,
fanning out across independent workflow runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move Run method steps to builtins with "use step" directives
Refactor: instead of manually registering Run method steps via
registerStepFunction in step-handler.ts, define them as proper "use step"
functions in builtins.ts with __builtin_ prefix. This leverages the
existing SWC plugin infrastructure — functions starting with "__builtin"
get stable bare-name step IDs.
- Add __builtin_run_{cancel,status,return_value,...} to both builtins files
- Use dynamic import() for getRun inside step bodies to avoid pulling
Node.js modules into the workflow bundle
- Remove manual registerStepFunction calls from step-handler.ts
- Update WorkflowRun step references to __builtin_run_* names
- Fix step name display in web observability: fall back to raw name
instead of "?" for built-in steps that don't follow step//module//fn format
- Add fibonacciWorkflow default args for nextjs-turbopack workbench UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Render Run objects as clickable links in web observability UI
- Add RunRef type and Run reviver to observabilityRevivers so serialized
Run objects are hydrated as RunRef instead of showing raw Uint8Array
- Add RunRefInline component (purple badge with run ID) that navigates
to the target run on click, matching the StreamRef pattern
- Thread onRunClick callback through the component chain:
WorkflowTraceViewer → EntityDetailPanel → AttributePanel → DataInspector
- Wire up navigation in the web app's run-detail-view
- Add startFromWorkflow default args for workbench UI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Throw error instead of silent fallback when Run class not in registry
Address PR review: the Run reviver now throws if the class isn't found
in the registry, instead of silently returning a plain { runId } object
that would break the assumption of getting a valid Run instance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix e2e failures: allow retries on Run getter steps, fix docs code samples
- Remove maxRetries=0 from read-only Run getter steps (status, returnValue,
workflowName, etc.) — these are safe to retry and need retries when the
child workflow hasn't completed within the step timeout. Only cancel
keeps maxRetries=0.
- Fix docs code samples: use correct import path (workflow/api not workflow),
add declare statements for helper functions used in examples.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use standard step//module//function naming for built-in steps
Update the SWC plugin's __builtin_ special case to generate proper
step//@workflow/core//{name} IDs instead of bare function names. This
makes parseStepName work correctly for built-in steps, showing:
- StepName: "Run#returnValue" (not "__builtin_run_return_value")
- ModuleSpecifier: "@workflow/core" (not the raw function name)
Convention: __builtin_Run_cancel → step//@workflow/core//Run#cancel
(uppercase prefix + underscore → instance method # notation)
- Move __workflow_start to builtins.ts as __builtin_start
- Rename __builtin_run_* to __builtin_Run_* for proper # notation
- Update WorkflowRun step refs to use full step// IDs
- Remove manual registerStepFunction from step-handler.ts
- Update SWC spec.md with new naming examples
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove SWC __builtin special case, use standard step naming for builtins
Remove the SWC plugin's __builtin_ special case so built-in steps get
standard step//{module}@{version}//{fn} IDs like any other step. This
makes parseStepName work correctly, showing proper StepName and
ModuleSpecifier in observability.
The VM reconstructs the same IDs via builtinStepId() which uses the
@workflow/core version to build: step//workflow/internal/builtins@{v}//{fn}
- Remove __builtin special case from SWC plugin (revert to original)
- Add builtinStepId() helper shared by workflow.ts, start.ts, run.ts
- Rename Run steps: __builtin_Run_cancel → Run_cancel, etc.
- Rename start step: __builtin_start → start
- Move start step from manual registerStepFunction to builtins.ts
- Keep __builtin_response_* names unchanged (pre-existing)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use static class methods for Run steps to get Run.method naming
Refactor Run method steps from standalone functions (Run_cancel) to
static methods on a Run class, so the SWC plugin generates step IDs
with the standard static method convention: Run.cancel, Run.returnValue,
Run.status, etc.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address PR review: tests, docs warnings, skill fix
- Add TODO on Run.returnValue about polling blocking (replace with system
hooks once AbortSignal/AbortController PR lands)
- Add docs callout warning about returnValue holding workers alive
- Fix SKILL.md contradiction that said start() can't be used in workflows
- Enhance suspension test to assert step arguments are forwarded
- Add WorkflowRun unit tests: serializable marker, runId, registry, delegation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix response builtins: adopt this-serialization from PR #1413
The rebase onto main didn't fully adopt PR #1413's refactor of response
builtins to use `this` instead of explicit parameters. The old pattern
(resJson(this) wrappers) passed `this` as an argument, but the step
functions now expect `this` to be set via method call context.
Switch to Object.defineProperties on Request/Response prototypes,
matching main's approach. Also document WORKFLOW_PUBLIC_MANIFEST=1
for local e2e testing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address docs review: returnValue polling is temporary, link to start() API ref
- Update returnValue warning to note this is a temporary implementation
that will be replaced with internal hooks
- Replace inline deploymentId: "latest" docs with link to the existing
start() API reference which already covers it comprehensively
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix e2e tests: replace collectedRunIds with trackRun API
PR #1426 replaced the manual collectedRunIds array with a trackRun()
helper. The start() wrapper already auto-tracks, so just remove the
manual push calls and add trackRun for the child run.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs: document error codes in errors and retries guide
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* changes
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
* docs: address PR review comments on error codes guide
- Add missing `start` import from `workflow/api`
- Note that `cause.code` may be undefined for older runs
- Remove invalid `--withData` flag and use `npx workflow` CLI style
- Remove empty changeset (docs-only change needs no changeset)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: correct WorkflowRunFailedError import and docs typecheck
- Import from `@workflow/errors` instead of `workflow` (not re-exported)
- Add WorkflowRunFailedError to import inference map
- Add myWorkflow placeholder to docs globals
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove import inference from docs typecheck, add explicit imports
Remove the import-inference system that auto-added imports to doc
snippets before typechecking. This was masking real issues — if a
snippet imported from the wrong module or was missing an import, the
typechecker silently fixed it instead of catching the bug.
All 24 affected doc snippets now have explicit, correct imports that
match what users would actually write.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove unused sleep import from rate limiting example
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add HookConflictError for hook token conflicts
Replace WorkflowRuntimeError with a dedicated HookConflictError class
for hook token conflicts. This correctly classifies the error as a
USER_ERROR (duplicate token is a user mistake) rather than a
RUNTIME_ERROR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use HookConflictError.is() instead of instanceof in docs
The .is() static method handles cross-VM/realm boundaries where
instanceof can be unreliable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add stepName field for human-readable step names
Slack-Thread: https://vercel.slack.com/archives/C09125LC4AX/p1773936163539109?thread_ts=1773936163.539109&cid=C09125LC4AX
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
* docs: add step examples with resolveData and hydration, document parseStepName/parseWorkflowName utilities
- Split step examples into two: one with resolveData: 'none' for efficient listing without data, and one showing how to hydrate input/output with observabilityRevivers
- Add TSDoc to stepName and workflowName fields explaining the machine-readable format and how to use parse utilities
- Document parseStepName() and parseWorkflowName() from @workflow/utils/parse-name for extracting shortName and moduleSpecifier for UI display
- Add Callout explaining the naming format
---------
Co-authored-by: v0 <v0[bot]@users.noreply.github.com>
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
* feat: classify run failure error codes and improve error logging
- Add RUN_ERROR_CODES (USER_ERROR, RUNTIME_ERROR) to @workflow/errors
- Populate errorCode in run_failed events via classifyRunError()
- Update web UI StatusBadge to show amber dot for infrastructure errors
- Improve world-local queue error logging (concise, no body dump)
- Improve schema validation error messages (concise, verbose behind DEBUG)
- Add e2e tests for error code flow and infrastructure error retry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add semantic error types to replace HTTP status code checks in runtime
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: classify run failure error codes and improve error logging
- Add RUN_ERROR_CODES (USER_ERROR, RUNTIME_ERROR) to @workflow/errors
- Populate errorCode in run_failed events via classifyRunError()
- Update web UI StatusBadge to show amber dot for infrastructure errors
- Improve world-local queue error logging (concise, no body dump)
- Improve schema validation error messages (concise, verbose behind DEBUG)
- Add e2e tests for error code flow and infrastructure error retry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: classify run failure error codes and improve error logging
- Add RUN_ERROR_CODES (USER_ERROR, RUNTIME_ERROR) to @workflow/errors
- Populate errorCode in run_failed events via classifyRunError()
- Update web UI StatusBadge to show amber dot for infrastructure errors
- Improve world-local queue error logging (concise, no body dump)
- Improve schema validation error messages (concise, verbose behind DEBUG)
- Add e2e tests for error code flow and infrastructure error retry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* address PR review comments
- Remove dead `meta` option from TooEarlyError constructor (TooTallNate)
- Extract `throwWithTrace` helper to deduplicate span recording in
world-vercel makeRequest (TooTallNate)
- Restore `maxAttempts` const for stable retry count logging (TooTallNate)
- Fix behavioral regression: add WorkflowAPIError 404 fallback in
suspension-handler hook disposal to handle world-vercel path where
makeRequest doesn't map 404 to HookNotFoundError (TooTallNate)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: translate 404 to HookNotFoundError at the world-vercel boundary
Move the 404 → HookNotFoundError translation into world-vercel's
createWorkflowRunEvent, where we know the event type context. For
hook-related events (hook_created, hook_disposed, hook_received,
hook_conflict), a 404 from the server means the hook was not found.
This removes the WorkflowAPIError 404 fallback from the runtime's
suspension-handler, keeping the runtime fully decoupled from HTTP
status codes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: parse Retry-After for 425 responses and narrow hook event set
- Parse Retry-After header unconditionally so TooEarlyError gets
the server-provided delay instead of always falling back to ~1s
- Narrow hookEventsRequiringExistence to only hook_disposed and
hook_received (matching world-local's set), since hook_created
and hook_conflict don't imply the hook must already exist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* rename WorkflowAPIError to WorkflowWorldError
Breaking change: rename WorkflowAPIError → WorkflowWorldError to
better reflect that this error represents world (storage backend)
failures, not HTTP API errors specifically. Updated across all
packages: errors, core, world-local, world-vercel, world-postgres,
workflow, and web.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [world-postgres] Fix TOCTOU race in step_started that corrupts event log
The step_started UPDATE had no conditional guard on step status, allowing
a concurrent execution to revert a completed step back to 'running'. This
caused duplicate step_completed events, triggering CORRUPTED_EVENT_LOG.
Add notInArray guard to match the existing pattern on step_completed and
the DynamoDB conditional expression used in the Vercel world.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [world-postgres] Add atomic terminal-state guards to all entity UPDATEs
Add conditional WHERE clauses to match the Vercel world's DynamoDB
conditional expressions, preventing TOCTOU races where concurrent
requests could bypass pre-validation and write invalid state transitions.
Changes:
- step_started: add NOT IN (completed, failed, cancelled) guard
- step_retrying: add terminal-state guard (was unguarded)
- step_completed/step_failed: add cancelled to guard
- run_completed/run_failed/run_cancelled: add terminal-state guards
- isStepTerminal: include cancelled status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [world-local] Add atomic terminal-state guards and concurrent race tests
Local world fixes:
- step_completed/step_failed: use writeExclusive lock to prevent
concurrent duplicate terminal transitions
- step_started: check for terminal lock file before allowing start
- wait_completed: use writeExclusive lock (port from PR #1388)
- isStepTerminal: include cancelled status
Tests:
- Concurrent step_completed race (exactly one succeeds, one gets 409)
- Concurrent step_failed race
- step_started rejection after concurrent step_completed
- Concurrent wait_completed race
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address PR review feedback
- Fix lock file extension: use .lock instead of .json via taggedPath to
avoid polluting entity directories with empty JSON files that cause
SyntaxError during listing/parsing
- Make startedAt update atomic using COALESCE in SQL instead of
deriving isFirstStart from the TOCTOU validation read
- Split changeset into separate entries per package
- Remove unnecessary context from changeset text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address review feedback from @TooTallNate
- step_started: replace fs.access() with a fresh re-read of the step
entity — honest about being best-effort rather than claiming atomicity
(local world is dev-only; postgres world has SQL-level atomic guards)
- wait_completed: clean up lock file on 404 to avoid leaked lock files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move lock files to .locks/ subdirectory off basedir
Lock files in entity directories (steps/, waits/) broke tests that
expect only tagged .json files. Move all locks to basedir/.locks/
and clean them up in clear().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pass ISO string to COALESCE instead of Date object
The postgres driver can't serialize a Date object inside a raw sql
template literal. Convert to ISO string for proper parameterization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: embed tag in lock file names for test isolation
Parallel vitest workers with different tags would collide on untagged
lock files, causing spurious 409s. Include the tag in the lock filename
(e.g. stepId.terminal.vitest-0) matching the pattern from PR #1388.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
* test: improve e2e test failure diagnostics with run context and GitHub annotations
When e2e tests fail, automatically dump workflow run diagnostics (status,
input/output, error details, event timeline, dashboard link) to the CI
logs. Emit GitHub Actions annotations that surface on PR file diffs.
Fix collectedRunIds which was declared but never populated, enabling
observability links in the PR comment. Enrich the aggregation script
to include run IDs and dashboard URLs for failed tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase diagnostics hook timeout and fix flaky Vercel Prod tests
- Increase onTestFailed hook timeout to 30s (default was 10s) so
diagnostics can fetch run data even after slow test timeouts
- parallelSleepWorkflow: increase elapsed threshold from 10s to 25s to
accommodate Vercel cold start latency
- webhookWorkflow: increase hook polling deadline from 30s to 60s and
test timeout from 60s to 120s for slow Vercel webhook registration
- readableStreamWorkflow: stop reading once expected content is received
instead of waiting for stream close (which can hang on Vercel), and
increase test timeout to 120s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: emit ::error annotations via process.stdout.write to bypass vitest ANSI prefix
Vitest's console interceptor prepends ANSI escape codes to console.log
output, which prevents GitHub Actions from parsing ::error workflow
commands. Use process.stdout.write() directly to ensure clean output.
Also enhance the custom reporter to emit annotations in onFinished
(which runs after vitest output is complete) as a reliable fallback,
and enrich failure data from the diagnostics sidecar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only show observability links for vercel-prod test failures
Community world and local tests don't run on Vercel's backend, so
dashboard links are meaningless for those categories. Previously,
test name collisions across sidecar files could cause community
test failures to show Vercel dashboard URLs from vercel-prod runs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: link annotations to test files instead of symlinked workflow sources
The workflow source files in workbench/ are symlinks that GitHub can't
resolve, causing annotations to show raw paths like #L0 instead of
linking to code. Now:
- utils.ts: omit file= from onTestFailed annotations (just show title)
- github-reporter.ts: use the actual test file path (e.g.
packages/core/e2e/e2e.test.ts) which GitHub can resolve
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add documentation for custom class serialization
* Enhance serialization docs: custom class examples with 'use step' instance methods
- Add custom classes to Supported Serializable Types with anchor link
- Replace verbose parenthetical in Requirements with back-link
- Add [!code highlight] to Basic Example serde methods
- Simplify geometry.ts to import Point instead of redefining it
- Rewrite Complex Example to demonstrate 'use step' on instance methods
with realistic APIs (database, HTTP) and contrast with workflow-context methods
- Add pass-by-value note for 'this' context with return-and-reassign pattern
* Fix docs typecheck: use correct @expect-error codes for Order examples
* Fix pre-existing docs typecheck failures in workflow-serde API reference
Add @skip-typecheck to bare static method signatures that aren't valid
standalone TypeScript.
* Revert extractor changes, use HTML comments for @expect-error markers
The docs typecheck extractor only supports HTML comment syntax for
@expect-error on main. Use <!-- @expect-error:XXXX --> instead of
{/* @expect-error:XXXX */} to match the existing convention.
* Use MDX comment syntax for @expect-error markers, add MDX support to extractor
MDX files don't support HTML comments (<!-- -->). The extractor's
@expect-error regex only supported HTML syntax, but @skip-typecheck
already supported both. This aligns @expect-error to also support
the MDX comment syntax ({/* @expect-error:XXXX */}).
* Set maxDuration: "max" in vc-config for workflow functions
* Use maxDuration: 60 for flow route, "max" for step/webhook
* Remove explicit maxDuration from webhook route, rely on plan default
* test: add e2e test proving sleep() works correctly inside loops
Validates a user-reported concern that sleep() inside a loop with step
calls fires all iterations instantly. The test confirms sleep is honored
on replay — 3 iterations with 3s sleeps take ~6s+ total elapsed time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review comments
- Fix misleading "3s per iteration" comment to "3s between iterations"
- Update test comment to reflect 2.5s jitter-tolerant threshold
- Add per-iteration delta assertions to ensure each individual sleep fires
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Apply suggestion from @TooTallNate
Co-authored-by: Nathan Rajlich <n@n8.io>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
---------
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Co-authored-by: Nathan Rajlich <n@n8.io>
* refactor: use `this` value serialization for builtin step functions
Refactor builtin step functions (__builtin_response_json, _text,
_array_buffer) to use `this` instead of an explicit parameter,
leveraging the useStep() proxy's this value serialization support.
- Assign useStep() proxies directly onto Request.prototype and
Response.prototype instead of wrapping them in class methods
- Update builtin function signatures to use `this: Request | Response`
- Remove unused duplicate builtins file from @workflow/core
(the canonical copy lives in workflow/src/internal/builtins.ts)
* use Object.defineProperties for non-enumerable prototype methods
* fix(swc-plugin): closure variable detection for `new` expressions and module-level declarations
Fix two SWC compiler plugin bugs related to closure variable detection:
1. Add Expr::New handling to ClosureVariableCollector so `new Class(...args)`
properly captures both the callee and arguments as closure variables.
2. Exclude module-level declarations (functions, variables, classes) from
closure variable detection, preventing over-capturing of identifiers
that are already available in all bundles. This also allows DCE to
properly remove step-only helpers and their imports from the workflow
bundle.
Fixes#1365
* fix(swc-plugin): handle additional expression/statement types in closure variable collector
Expand closure variable detection to cover more AST patterns:
Expressions: Seq (comma), Yield, OptChain, Prop::Shorthand,
computed property keys, Prop::Assign defaults, Class (skip bodies)
Statements: Throw, Try/Catch/Finally, Switch, ForIn, ForOf,
DoWhile, Labeled
Also fix existing Prop::Shorthand bug where object shorthand
properties like { url } were not being collected as closure vars.
Extend test fixture with cases for all newly handled patterns.
Fix spec.md wording per review feedback.
* fix(swc-plugin): preserve original step function bodies in enclosing functions
In step mode, nested step functions were replaced with bare references
to the hoisted copy (e.g., `return hoisted$fn;`). This broke direct
calls because the hoisted copy uses `__private_getClosureVars()` which
only works in workflow context.
Now the original function body is preserved inline with just the
directive stripped, so JavaScript's normal closure semantics work for
direct calls. The hoisted copy with `__private_getClosureVars()` is
still registered for workflow-driven execution.
Fixes#1369
* fix(swc-plugin): restore metadata tracking for object property steps in step mode
The previous commit accidentally removed the
object_property_workflow_conversions tracking from the step mode path,
causing __internal_workflows metadata to be stripped from step bundle
output for object property step functions.
* fix(swc-plugin): detect closure variables inside nested function/method bodies
The closure variable collector was skipping nested function expressions,
arrow functions, and method bodies entirely. This meant closure
variables used deep inside inner functions (e.g., a variable used inside
a ReadableStream's start() method) were not captured.
Now the collector walks into nested function/arrow/method/getter/setter
bodies while adding their parameters to the local var set, so only
truly free variables from the outer step scope are captured.
Also add ReadableStream, WritableStream, TransformStream, and other
common Web API globals to the known globals list.
* update changeset to include Bug 4
* fix(swc-plugin): handle TypeScript expression wrappers and class bodies in closure detection
After comparing with Next.js's SWC plugin closure detection approach,
identified and fixed remaining gaps:
- TypeScript expression wrappers (as, satisfies, !, type assertions,
const assertions, instantiation expressions) now traverse to the
inner expression instead of being silently skipped
- Class expressions and declarations now walk their body members
(methods, properties, constructors, static blocks) to detect
closure variables used inside them
- Document all remaining safe-to-skip Expr variants (This, Lit,
SuperProp, MetaProp, PrivateName, Invalid, JSX)
* test: add fixture cases for TypeScript wrappers and class body closure detection
* test: add TypeScript fixture for closure detection through TS expression wrappers
Add a proper input.ts fixture that tests closure variable detection
through real TypeScript syntax: `as`, `satisfies`, `!` (non-null),
angle-bracket type assertions, `as const`, and generic function calls.
Update test harness to support input.ts files by adding swc_ecma_parser
dev-dependency and auto-detecting TypeScript syntax from file extension.
Remove the incorrectly placed TypeScript-related test cases from the JS
fixture (they were using plain JS syntax, not actual TS wrappers).
Apply messages override before system override so that the system
message is prepended to the new prompt instead of being discarded.
Previously, system was applied first then messages replaced the
entire conversation prompt, losing the system message.
Add tests for prepareStep system/messages ordering:
- system only: prepended to prompt
- system + messages: system prepended to replaced messages
- system + messages with existing system: replaces existing
- system updates across multi-step tool call rounds
* fix trace viewer for v1 events
* add changeset
* add a test
* add a test for web-shared
* add lock file
* update logic to look for run_created event first
* handle step created
* add changeset