Add a `--url` flag to `inspect`/`web` that prints a run's observability
dashboard deep link to stdout and exits — no browser, no local server —
so scripts and agents can share a link instead of opening a UI.
Fix the Vercel dashboard URL to the current
`…/workflows/runs/<id>?environment=<env>` route (drop the legacy
`/observability` segment) and respect `--env`. Apply the same route fix
to the e2e helpers, CI aggregation scripts, and the nextjs-turbopack
workbench. Document deep-linking in the workflow skill and observability
docs.
Signed-off-by: Pranay Prakash <pranay.gp@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* 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>
The health command's `endpoint` flag and the shared `env` flag both
declared `char: 'e'`, causing ambiguity. Remove the short flag from
`endpoint` so `-e` unambiguously maps to `--env`.
The e2e tests spawn a CLI subprocess for every inspect/cancel/health call.
Each subprocess performs an npm registry version check on startup, which
can hang under load and exceed the 20s spawn timeout, causing SIGTERM.
Add WORKFLOW_NO_UPDATE_CHECK=1 env var support to skip the check, and
set it in the e2e test harness.
Multiple layered issues caused encryption key 429 errors to produce
confusing, unrelated-looking test failures:
- awaitCommand() in e2e utils now rejects on non-zero exit codes
instead of silently resolving (the most critical fix)
- cliInspectJson/cliHealthJson throw on empty stdout instead of
falling back to '{}'
- maybeDecryptFields re-throws HTTP errors instead of silently
falling back to encrypted placeholders
- fetchRunKey includes response body and status text in errors
- Added 429 to CLI status text map
* Stop reading WORKFLOW_VERCEL_* env vars at runtime to prevent unintended proxy routing
createWorld() in core no longer reads WORKFLOW_VERCEL_PROJECT, WORKFLOW_VERCEL_TEAM,
WORKFLOW_VERCEL_AUTH_TOKEN, WORKFLOW_VERCEL_ENV, or WORKFLOW_VERCEL_PROJECT_NAME from
process.env. These env vars are intended for CLI/observability tooling only, and
reading them at runtime caused all workflow traffic to route through the
api.vercel.com proxy when users mistakenly set them as Vercel project env vars.
The Vercel runtime already provides everything needed: OIDC tokens for auth,
VERCEL_PROJECT_ID for encryption context, and VERCEL_DEPLOYMENT_ID for world
selection. createWorld() now calls createVercelWorld() with no config.
A warning is emitted if the env vars are detected at runtime, telling users to
remove them.
The CLI and e2e tests are updated to call createVercelWorld() directly with an
explicit config object and inject via setWorld(), keeping the WORKFLOW_VERCEL_*
env vars scoped to tooling contexts only.
* Refactor inferVercelEnvVars to return config instead of relying on process.env
inferVercelEnvVars() now returns a VercelEnvVars object with the resolved
config values. setup.ts uses the returned object directly for
createVercelWorld() instead of re-reading from process.env via getEnvVars().
The writeEnvVars() calls inside inferVercelEnvVars are consolidated into a
single call at the end, retained only for the embedded web UI which reads
process.env as a fallback in its server actions. The scattered writeEnvVars
calls after each inference step are removed.
* Address review: consistent e2e gate and expanded warning
- Use WORKFLOW_VERCEL_ENV as the gate in both e2e.test.ts and
bench.bench.ts for consistency (was WORKFLOW_VERCEL_AUTH_TOKEN in
e2e.test.ts, WORKFLOW_VERCEL_ENV in bench.bench.ts)
- Expand the misconfiguration warning to also detect
WORKFLOW_VERCEL_AUTH_TOKEN and WORKFLOW_VERCEL_ENV, listing the
specific env vars that are set
* Add changeset
Signed-off-by: Nathan Rajlich <n@n8.io>
---------
Signed-off-by: Nathan Rajlich <n@n8.io>
CLI showStream:
- Requires --run with --decrypt for encrypted stream decryption
- Warns when --decrypt is used without --run
Web stream reading:
- readStreamServerAction accepts runId parameter for key resolution
- Stream API route reads runId from query param
- readStream client function passes runId to the API route
- useStreamReader hook accepts and passes runId
- run-detail-view passes runId to useStreamReader
Removes getRunIdFromStreamId helper (stream IDs don't always share
the run's ULID, e.g. streams serialized across step/workflow boundaries).
The Vercel CLI's repo.json format puts orgId on each project entry,
not at the root level. The CLI was reading repoConfig.orgId (undefined)
instead of project.orgId, which meant teamId was never set. Without
teamId, the world-vercel URL selector used the direct vercel-workflow.com
URL instead of the api.vercel.com proxy, causing 401 errors because
vercel-workflow.com only accepts OIDC tokens (not CLI auth tokens).
Also fix the RepoProjectConfig/RepoProjectsConfig type definitions
to match the actual repo.json structure.
* Add browser-compatible AES-GCM to core and HKDF key derivation to world-vercel
* update changeset
* Move HKDF key derivation server-side: API returns per-run derived key
* Refactor encrypt/decrypt to accept CryptoKey, export importKey for callers to import once per run
* Overload getEncryptionKeyForRun: accept context for start(), fetch WorkflowRun in resume-hook
* Split changeset into per-package descriptions for world, world-vercel, and core
* Remove unnecessary Uint8Array.from() wrapper around Buffer.from()
* Use zod to parse Vercel API response
* fix: restore world-vercel files to main versions
The rebase incorrectly picked up older versions of these files from
early encryption branch commits. The main versions are correct and
up-to-date.
* fix: add type cast for hydrateStepReturnValue return in hook.ts
* Make decryption an explicit opt-in for o11y tooling
* Restore encrypted data handling in o11y hydration layer
* Use EncryptedDataRef with util.inspect.custom for CLI encrypted data display
* Fix Decrypt button crash: use correct 'refresh' callback from useWorkflowResourceData
* Implement client-side decryption for web o11y with getEncryptionKeyForRun RPC
* Fix CLI decrypt: fetch WorkflowRun for key resolution, cache per runId
* Use named constructor pattern for encrypted data display in web o11y
* Decrypt event data when encryption key is available after Decrypt button click
* Lift encryption key to run-level state, auto-decrypt on fetch, fix field pollution
* Re-load expanded event data when encryption key becomes available
* Consolidate Decrypt to title bar Button, remove sidebar decrypt card
* Add hover tooltip to Decrypt button explaining scope and state
* Show flat Encrypted label for encrypted fields, use Lucide Lock icon in DataInspector
* Render eventData subfields individually to avoid encrypted markers in collapsed preview
* Revert: render eventData subfields individually
* Fix Lock icon vertical alignment in DataInspector encrypted label
* update changeset
* Update CLI, web, and stream callers for CryptoKey: importKey at resolution sites
* Pass teamId to the get-key endpoint
* fix: remove unused DataInspector import in events-list.tsx
* fix: restore world-vercel files to base branch versions
Cherry-pick conflict resolution incorrectly took the older opt-in-decrypt
versions of these files, reverting improvements from main (dispatcher,
createGetEncryptionKeyForRun extraction, nullable key response).
* fix: address PR review feedback
- Remove duplicate AttributePanel/EventsList rendering in entity-detail-panel.tsx.
Thread encryptionKey into the existing EventsList render instead.
- Restore missing re-exports (isClassInstanceRef, isStreamId, isStreamRef)
in web-shared/src/index.ts to maintain backwards compatibility.
- Add 'error' to replaceEncryptedWithMarkers field list in web-shared
hydration.ts to match the decrypt path.
- Extend CLI hydration eventData decrypt/placeholder to cover all known
serialized fields (output, metadata, payload) not just result/input.
- Add 'error' to CLI replaceEncryptedWithRef field list.
- Remove invalid encryptionKey option from useWorkflowResourceData call
(hook doesn't support it yet), add TODO.
- Add 4 unit tests for hydrateDataWithKey in serialization-format.test.ts:
encrypted+key decrypts, encrypted+noKey returns raw, non-encrypted
hydrates normally, non-Uint8Array legacy data passes through.
* feat: thread encryptionKey through useWorkflowResourceData hook
Instead of leaving a TODO, implement the encryptionKey support directly:
- Add optional encryptionKey to useWorkflowResourceData options
- When key is available, use hydrateResourceIOWithKey (async decrypt)
instead of hydrateResourceIO for all resource types
- Remove redundant hydrateResourceIO from fetchResourceWithCorrelationId
* fix: address comprehensive review feedback on PR #1256
High priority:
- Gate showStream key fetch on --decrypt flag, warn when --decrypt
used without --run
- Fix workflow-server-actions.server.ts missing cryptoKey params
(undefined for both getExternalRevivers and getDeserializeStream)
- Add hydration + decryption to listEvents (was completely missing)
- Fix error/eventData display: check isEncryptedMarker before
hasDisplayContent so encrypted markers don't silently disappear
Medium priority:
- handleDecrypt: use toast.error() instead of console.error for
user-visible feedback on key fetch failures
- CLI maybeDecryptFields: add try/catch with graceful fallback to
encrypted placeholders + warning, also decrypt error field
- use-resource-data: wrap hook/sleep hydrate() in try/catch to
prevent stuck loading state on decryption errors
- Decrypt button: also check run.error and step input/output for
encrypted markers, not just run.input/output
Low priority:
- event-list-view: add .catch() to re-load useEffect promise
- Export ENCRYPTED_DISPLAY_NAME from hydration.ts and import in
data-inspector.tsx instead of raw 'Encrypted' string