3.8 is the release where Elsa grew up operationally. Safe-by-default security, graceful shutdown, a real observability stack, first-class secrets, single sign-on — plus an AI copilot and a dashboard API for Studio.
This is the release candidate for 3.8.0, covering everything since 3.7.1: 114 pull requests across two pre-releases. The theme running through most of it is running Elsa in production and being able to tell what it's doing — the runtime now drains instead of dying, logs and traces are first-class, dispatch survives a crash, and a set of dangerous defaults have been closed off.
That last part matters before you upgrade: 3.8 removes several production-usable defaults, and a host that relied on them will now refuse to start. That's deliberate — see Upgrade notes first, then come back.
| Commits | 323 |
| Pull requests | 114 |
| Files touched | 1,948 (+143,056 / −3,042) |
| New modules | 47 (38 → 85 total) |
| New contributors | 4 |
| Compare | 3.7.1...3.8.0-rc1
|
Highlights
1. Security: several defaults were unsafe, and are now gone
A dedicated remediation pass ran across identity, expressions, and the API surface. Most of it is invisible; the parts that aren't will stop a misconfigured production host at startup, on purpose.
Defaults that no longer ship. Default admin credentials are now development-only — outside Development, username, password, and API key must be configured explicitly (#7500). Known public/default JWT signing keys are rejected outside explicit Development or Demo mode, failing fast with an OptionsValidationException rather than quietly signing tokens anyone can forge (#7496). Localhost permission grants are opt-in instead of a production default (#7498).
Scripting is now a privileged capability. Roslyn C# and Python.NET expressions execute host code — they are not sandboxes, and 3.8 stops pretending otherwise. Both are disabled unless the host explicitly opts in, their descriptors and authoring surfaces are hidden when it hasn't, and author/publish/dispatch/execute paths carrying scripts require dedicated permissions (exec:csharp-expressions, and the Python equivalent). Python and C# share one preflight authorization path. (#7519 · #7507, fixes #7096)
Identity internals. Random string and API-key generation moved to cryptographic RNG, and new secrets hash with versioned PBKDF2-SHA256 and per-record salts. Legacy SHA-256 hashes stay verifiable and are upgraded in place after a successful validation — no forced reset (#7511). Issued JWTs carry a token_use claim so a refresh token can no longer be presented as an API access token; /identity/refresh-token gets its own scheme (#7509).
Authorization gaps closed across workflow imports (#7510), role assignment (#7501), SignalR workflow-instance observation (#7504), the console-logs hub (#7531 · #7533), and the resilience simulate-response endpoint (#7505). Bookmark-resume SAS tokens now fail closed before input parsing, ZIP cache download IDs are validated as opaque tokens with path containment enforced (#7495), HTTP workflow request body limits are enforced while reading rather than after (#7497), workflow timestamp filter columns are whitelisted (#7506), HTTP bookmark lookup is scoped to tenant (#7508), and polymorphic workflow JSON no longer resolves arbitrary types through unrestricted Type.GetType (#7499).
Read Upgrade notes before deploying. Four of these will stop a host or a workflow that worked on 3.7.1.
2. Graceful shutdown: the runtime drains instead of dying
On host stop or shell deactivation, in-flight workflow executions now get a bounded chance to finish their current burst before the process exits — and anything cut short is recovered automatically on the next start.
The mechanism is worth understanding because it's extensible:
IQuiescenceSignalcomposes two flags —Drain(forward-only, triggered by host stop) andAdministrativePause(reversible, operator-triggered). Both are idempotent; pause can optionally persist viaIKeyValueStoreso it survives a restart.IIngressSourceis the contract every component that injects external events implements — HTTP, scheduling, message consumers, internal workers, and third-party modules alike. Drain pauses all sources in parallel with per-source timeouts, escalating toIForceStoppablewhere available. A source that claimsPausedand then starts a burst anyway is detected and flipped toPauseFailedrather than trusted.BurstTrackingMiddlewareregisters a handle for the lifetime of every execution burst, so the orchestrator can wait on real work rather than a guess. The wait is deadline-clamped; on breach, bursts are force-cancelled, instances persist asInterrupted, and aWorkflowInterruptedlog entry is written.- Recovery on next activation scans for
Interruptedinstances — a scan deliberately disjoint from the existing timeout-basedRestartInterruptedWorkflowsTask, so the two don't fight. - The heartbeat deliberately outlives drain, so a draining node doesn't look dead to the cluster mid-drain.
There's also an authenticated admin surface in Elsa.Workflows.Api — GET /admin/workflow-runtime/status plus POST to pause, resume, and force-drain — letting operators quiesce a node without stopping the host. While paused, HTTP middleware short-circuits to 503 with Retry-After instead of accepting work it won't run. First-party ingress adapters ship for HTTP triggers and scheduled triggers.
This is on by default — UseWorkflowRuntime() registers the drain hosted service unconditionally. The drain deadline defaults to 30 seconds and is automatically clamped to the host's own shutdown budget less a 500 ms safety margin, so the runtime never gets killed mid-persistence by a shutdown timeout it was already exceeding. Tuning knobs live on GracefulShutdownOptions: DrainDeadline, per-source IngressPauseTimeout (5 s), stimulus-queue depth while paused (10,000, Buffer overflow policy), and whether an administrative pause survives a runtime generation boundary. (#7424)
3. You can finally see what's happening
Four independent pieces landed that, together, make an Elsa host observable without attaching a debugger.
Live server logs — Elsa.Diagnostics captures ILogger events, redacts sensitive values, keeps a bounded recent-log buffer, and streams live over SignalR. Filterable by level, category, text, tenant, workflow, trace/correlation ID, source, and time. Source metadata is tracked per instance, so clustered and containerized deployments can tell nodes apart, with notifications when a new source appears. (#7438)
Console logs — a separate opt-in module capturing raw stdout/stderr, behind read:diagnostics:console-logs, with the same redaction, filtering, and bounded buffering. Later work scoped console output to individual workflow instances, so you can read what a specific run printed. (#7462 · #7535 · #7536 · #7542)
Structured log persistence — SQLite-backed storage for structured logs, with storage diagnostics exposed so you can see whether the log store itself is healthy. (#7445 · #7446)
OpenTelemetry — a first-party Elsa.Workflows ActivitySource with spans around workflow execution cycles and activity execution, meter instruments for workflow started/completed/faulted counts and activity duration, and W3C trace-context propagation on outbound SendHttpRequest / FlowSendHttpRequest calls. Source and meter names are documented, so your collector config is stable. (#7514, closes #7489)
Readiness probes — opt-in health checks covering workflow runtime state, workflow persistence reachability, and distributed lock provider reachability, wired as separate liveness and readiness endpoints with documented Kubernetes probe recommendations. / stays process-liveness compatible. (#7513, fixes #7490)
4. Secrets have a home
Until now, a credential in a workflow was a string in a workflow. Elsa.Secrets introduces immutable named secrets with metadata-only APIs, runtime resolution, and built-in encrypted and configuration-backed stores, plus EF Core persistence. (#7468 · #7572)
The design point is that the secret value never enters the workflow definition:
- A
Secretexpression stores aSecretReference, not a resolved value, and resolves throughISecretResolverat execution time — so definitions round-trip through JSON without ever carrying the secret (#7574). getSecret(name)is available in JavaScript expressions via Jint, typed asPromise<string>, composable with.thenor an async IIFE (#7575).- Sensitive inputs are excluded from persisted state.
InputAttribute.CanContainSecretsnow maps toInputDescriptor.IsSensitive, flows through to the API client model for Studio, and evaluated sensitive values are actively removed fromActivityStaterather than merely skipped (#7573). - Adoption was validated end to end through the HTTP Request
Authorizationheader path, with documentation on rotation, scopes, types, and leakage (#7576).
5. Single sign-on: bring your own identity provider
The largest single change in the release — 272 files — is an Elsa-owned, protocol-neutral external authentication broker. Multiple login methods can be active at once, and identity-provider connections are managed host-wide from configuration or from the database.
- OpenID Connect v2 with exact discovery, confidential client authentication, mandatory S256 PKCE, deployment-derived callback URLs, upstream logout controls, and minimal upstream token retention.
- Configuration vs. database ownership, made explicit. Configuration-owned connections are read-only. Database overrides are complete shadows: disabling an override keeps it shadowing, archiving reveals the underlying configuration, restoring resumes shadowing. No ambiguous merge semantics.
- Immutable logical connection keys for callbacks, identity links, and sessions — so re-creating a connection record doesn't orphan live sessions.
- Account linking via pluggable matchers. The generic matcher auto-links only on a single unambiguous match; ambiguity and errors are rejected rather than guessed.
- Credentialless JIT users with static default roles, plus role-deletion dependency protection.
Elsa's own authorization stays separate from the upstream IdP: the broker owns protocol and security invariants, and future adapters (GitHub, Microsoft, social logins) contribute descriptors without changing the connection envelope.
Two follow-ups deserve their own mention. #7903 excludes archived connections from shadow relationships and fixes secret handling for newly created connections. #7905 came out of a deliberate hunt for failure windows and closed several: user-deletion restoration is non-cancellable once begun, and external-identity link publication reconciles across cancellation, concurrent deletion, and lost commit acknowledgements — the case where the database committed but the client never found out. Without it, a badly-timed cancellation could strand an orphaned JIT user or a dangling identity link. Credentials are never issued from an indeterminate state.
6. Weaver — an AI copilot that runs on the server
Weaver is a workflow-aware AI assistant designed to sit behind a chat panel in Elsa Studio. The interesting part isn't that it exists; it's where it runs and what it's allowed to do.
The server owns the model conversation. Studio never talks to an AI provider directly. The Elsa host resolves context, redacts sensitive values before anything reaches the model, streams message deltas and tool-lifecycle events back, and persists the audit trail. Provider credentials stay on the server.
Mutations are proposals, not actions. Weaver cannot write a workflow. It produces a proposal — workflow payload plus rationale, validation diagnostics, warnings, and a graph diff — that a human reviews and applies. Proposals with validation errors are blocked at apply time. Every approval records actor, tenant, and the applied change.
Tools are governed by default. Read-only module tools auto-enable; proposal, administrative, and MCP-contributed tools require explicit enablement, and administrative tools can be disabled by name.
The agent loop runs on the GitHub Copilot SDK, which owns session create/resume and streaming while Elsa registers its tools as governed SDK callbacks. #7704 adds the grounding layer — read-only tools over activities, definitions, runtime instances, and incidents — so answers are anchored in your system rather than the model's priors. State persists across SQL Server, PostgreSQL, MySQL, Oracle, and SQLite.
Status: Weaver is new and evolving. The server side is here; the Studio experience ships separately. Treat it as preview surface for 3.8.
Also new
Operational dashboard API. Elsa.Dashboard.Api exposes /dashboard/overview, /workflow-trends, /needs-attention, /recent-activity, and /workflow-hotspots for Studio's operational dashboard. The design decision worth noting is what happened after the first cut: rather than have the dashboard module reach into workflows and logs directly, Elsa.Dashboard.Abstractions defines contributor contracts, each owning module contributes its own data, and the API composes them with per-contributor failure isolation — a broken log provider degrades one panel instead of 500-ing the page. Contributors then split into companion modules (Elsa.Workflows.Runtime.Dashboard, Elsa.Diagnostics.ConsoleLogs.Dashboard, Elsa.Diagnostics.StructuredLogs.Dashboard), so you can ship a runtime without its dashboard surface — and your own modules can contribute panels without the dashboard module knowing they exist. (#7669 · #7681 · #7690 · #7692)
Webhook triggers. A new Elsa.Http.Webhooks module adds a WebhookEventReceived trigger activity backed by a webhook receiver endpoint, with an activity provider that surfaces registered webhook events as first-class activities in the designer. Workflows can now start from an inbound webhook without hand-rolling an HTTP endpoint and correlation.
State machine activity. StateMachine, StateMachineState, and Transition in Elsa.Workflows.Core, with state entry/exit activities, transition triggers, conditions, and actions. Competing outbound triggers are cancelled when a transition wins; states with no valid outbound transitions are terminal; a failed transition trigger re-arms when its condition evaluates false. Backend only — the Studio/X6 designer work is follow-up. (#7457, implements #5085)
Ingress rate limiting hooks. Opt-in ASP.NET Core rate limiter policies for Elsa API prefixes and HTTP workflow trigger base paths, shipped disabled with documented tuning and custom-policy paths. (#7512, fixes #7488)
Elsa Platform integration. A new Elsa.Platform.Integration module for hosts managed by the Elsa Platform: recipe-artifact application, a deployment worker, shell configuration overlays, capability verification, and diagnostic sanitization. Not needed for self-hosted deployments.
Persistence vNext (experimental). A provider-neutral persistence layer landed end to end as a proof of concept: database-agnostic schema/storage/index descriptors, relational planning with SQLite and SQL Server renderers, portable document-store contracts, SQL Server / PostgreSQL / MongoDB POCs, a schema catalog with startup materialization, runtime-defined entities, and a physicalization planner. Nothing changes for you — it's additive and experimental, existing EF Core persistence is untouched, and workflow runtime hot paths are explicitly not migrated pending benchmarks. It's here so the design can be reviewed against real code instead of a document. (#7680 · roadmap #7580)
Reliability: the bugs you actually hit
Not losing work
"My workflow committed some things but not others." Workflow commit persistence is now wrapped in a provider-safe transaction (SQL Server EF Core; other providers keep the previous no-op behavior rather than silently opting into ambient transactions they may not support). Commit-time mediator notifications are buffered until persistence succeeds, and activity context / log cleanup is deferred until commit success — so a failed commit no longer fires notifications for work that didn't land. (#7748, fixes #7726 / #5961)
"A dispatch vanished between commit and enqueue." An opt-in transactional dispatch outbox, backed by the existing key-value store, gates delivery on a committed owner workflow-state marker — closing the window where a crash between commit and enqueue silently lost the dispatch. Workflow definition dispatch is now idempotent by generated child instance ID. (#7517, closes #7493)
"A bookmark queue item failed and disappeared." Repeatedly failing and expired queue items now move to a dead-letter store instead of being deleted, with REST endpoints to list, inspect, delete, and replay them under dedicated permissions, and EF Core migrations for every supported provider. (#7516, closes #7491)
"Some bookmark queue items were never processed." Queue paging skipped later items when processed or dead-lettered rows were deleted mid-page. Fixed, along with re-signalling distributed queue processing after transient lock misses; the default purge TTL is increased to reduce the lock/purge race. (#7748)
Startup, shutdown, and tenancy
"Restarting the host caused a thundering herd." Startup loaded the entire scheduling backlog at once, then fired every past-due Delay/Timer/StartAt bookmark ~1 ms later. Triggers and bookmarks now rebuild in configured pages, and a past-due staggerer spreads catch-up across a bounded window. Includes efficient EF count paths and a paged-trigger cache-key fix. (#7746, closes #7735)
"Interrupted workflows restarted forever." Instances left with IsExecuting = 1 but WorkflowStatus = Finished were repeatedly picked up by the restart scan, producing an infinite cycle. Finished workflows are now excluded from the filter. (#7435 by @jwdb)
"OverflowException during host shutdown." DefaultTenantService mutated live tenant dictionaries during deactivation without the semaphore refresh used, and lazy initialization published dictionaries before populating them. Mutations are now serialized, lifecycle changes wait for initialization, reads stay ungated, and the synchronization primitives survive teardown — no more ObjectDisposedException from the shutdown barrier. (#7898, closes #7771)
"An exception in my middleware turned into ObjectDisposedException and hid the real error." TenantResolutionMiddleware restored the original HttpContext.RequestServices only on the success path. It now restores in a finally, so outer exception handlers get a live provider and the original exception propagates unobscured. (#7901 by @DenDeline, fixes #7900)
"Startup got slower with every tenant." Built-in activity providers ran twice per tenant — 2N calls for N tenants — despite their descriptors not depending on the tenant. Providers can now opt into ITenantAgnosticActivityProvider (applied to TypedActivityProvider and HostMethodActivityProvider) and initialize once per registry instance. Tenant-sensitive providers, including WorkflowDefinitionActivityProvider, still refresh every pass; third-party implementations keep their existing behavior unless they opt in. (#7904, fixes #7306)
Execution correctness
"Fork threw InvalidCastException after a workflow resumed." Fork's "Completed" tracking is a HashSet<string>, but restored state materialized it as List<string>, and the cast failed before join-mode evaluation could run. Fork's original state shape is now preserved across persistence, and enumerable-to-set conversion is fixed during restore. (#7431)
"Complete inside a ForEach body faulted with 'not reachable from the flowchart graph'." In counter-based flowchart mode, an ancestor flowchart treated a cancellation signal from a nested body flowchart as graph-local skip propagation. Loop composites are now marked breaking when Complete completes them, so a pending body-completion callback can't schedule another iteration. (#7702, fixes #7693)
"C# expressions failed when reading JSON variables." RunCSharp accessing a JSON-backed variable through the generated Variables accessor pulled in a System.Dynamic dependency that the same feature path didn't register, breaking script compilation. (#7415 by @RalfvandenBurg · #7416, fixes #7414)
"Reverting a workflow version produced the wrong number." RevertVersionAsync allocated the next version from VersionOptions.Latest (the definition flagged IsLatest) rather than the highest stored version. Those normally agree — but when they diverge, the revert could land at or below an existing version. It now uses FindLastVersionAsync for numbering, matching SaveDraftAsync, while VersionOptions.Latest keeps owning latest-state management. (#7917 by @Shivamkmr8, addresses #7916)
"Oracle upserts failed with ORA-00904." GenerateOracleUpsert emitted unquoted identifiers, which Oracle folds to uppercase — while Elsa's Oracle migrations create quoted mixed-case objects like "Elsa"."ActivityExecutionRecords". A second failure lurked in SELECT ... FROM DUAL, where ODP.NET has no target column to infer from and defaults .NET strings to VARCHAR2 against Elsa's NVARCHAR2 mapping. Identifiers now route through the provider's ISqlGenerationHelper, and parameters cast via the EF Core column type. Only the Oracle generator changed. (#7756 by @MohitGuptaC, fixes #7755)
"Activity descriptors disappeared after a refresh." A provider refresh returning no tenant groups wiped existing descriptors; transient empty refreshes now preserve them. (#7550)
HTTP workflow edge cases. HttpContext.RequestAborted is restored via try/finally after timed workflow failures (#7712); fault handling falls back to in-memory state when reloading the instance returns null (#7714); parsed activity JsonDocuments are disposed deterministically (#7713); and a missing NotFoundActivity descriptor throws a clear InvalidOperationException instead of a null-forgiven crash (#7711).
Azure Service Bus subscription churn. Application instance names can come from a configured stable source instead of being randomized per start, with a random fallback when nothing is configured. Ported across 3.7.1, 3.8, and main. (#7742 · #7743 · #7744)
Operability and performance
Runtime status without the keys to the kingdom. A dedicated read:workflow-runtime permission covers runtime status reads. Pause, resume, and force-drain stay behind ManageWorkflowRuntime, which is still accepted on status endpoints for backward compatibility — so read-only dashboards and monitors no longer need an operator-grade token. (#7729, closes #7727)
Rehydration stopped being silent. WorkflowStateExtractor deliberately skips unresolved activity execution contexts to preserve migration compatibility — but it did so without a word, which also concealed genuinely stale state at the same definition version. It now emits structured warnings classifying each skip as migration-compatible or unexpected (comparing persisted and target definition version IDs) with workflow, definition, context, owner, and child identifiers for querying and alerting. Behavior is unchanged; visibility isn't. (#7899, fixes #7772)
Activity registry lookups are O(1). ActivityRegistry.Find(string type) scanned every descriptor and MaxBy'd on every call, on hot paths. It now uses a per-registry latest-descriptor index that preserves tenant-specific precedence over agnostic descriptors and stays consistent across add, remove, clear, provider clear, tenant clear, and refresh — including removing descriptors from registries a provider no longer contributes to, so stale latest-by-type entries can't survive. (#7538 · #7521)
Every module feature is categorized. All 312 generated shell-feature manifest entries carry a category — workflows, persistence, diagnostics, AI, identity/security, tenancy, HTTP, expressions/scripting, dashboards, storage, caching, infrastructure — with zero uncategorized, so tooling that composes Elsa hosts can group and filter meaningfully. (#7463 · #7699)
Distributed lock provider validation at startup, so a misconfigured provider fails loudly rather than silently degrading (#7515). API keys work in tenant-agnostic contexts via a new ApplicationFilter.TenantAgnostic (#7705, closes #7579).
Smaller things
WithVariable<T>(string name)— a typed, named variable overload that doesn't force a default value. The obsolete parameterless overload pointed people toward one that still demanded one, which is awkward for output-bound variables. (#7701, addresses #7694)ManagementOptions.FailOnValidationErrors— opt out of publish-fails-on-validation-errors when you intentionally leave required properties blank (an empty Cron expression to disable a trigger, say). Default staystrue; errors remain onresult.ValidationErrors. See Upgrade notes — this one also changes an HTTP status code. (#7741)- A missing
[Obsolete]onIWorkflowBuilder(#7448 by @heku). - Dependency hygiene — Microsoft package bands to 9.0.17 / 10.0.9, plus MongoDB.Driver,
System.Security.Cryptography.Xml,System.Linq.Dynamic.Core, and SQLite native pins (#7459 · #7547 · #7766 · #7896). - A codebase wiki now generated and refreshed automatically from the repository (#7453 · #7454).
Upgrade notes
3.8 has more breaking changes than a typical minor release, concentrated in security defaults. Most are a configuration change, not a code change — but a host that relied on the old defaults will fail at startup, which is the intended outcome.
⚠️ Will stop a host that worked on 3.7.1
Default admin credentials are gone outside Development. Username, password, and API key must be configured explicitly in any non-Development environment. (#7500)
Known default JWT signing keys are rejected. Outside explicit Development or Demo mode, a host configured with a public/default signing key fails fast with OptionsValidationException. If you shipped the sample key to production, this is the release that stops you — and you should treat any tokens signed with it as compromised. (#7496)
C# and Python expressions are opt-in. Roslyn C# scripting requires CSharpOptions.AllowHostCodeExecution; Python.NET requires the equivalent host opt-in. Without it, the descriptors are hidden and existing workflows containing RunCSharp or Python expressions will not author, publish, dispatch, or execute. API paths carrying scripts additionally require exec:csharp-expressions (and the Python equivalent). Neither is a sandbox — that's precisely why they're now privileged. (#7519 · #7507)
Localhost authorization grants are opt-in. Previously a production default. Enable explicitly for local development scenarios. (#7498)
⚠️ Breaking API and contract changes
IWorkflowJsonTypeRegistry is removed. It and WorkflowJsonTypeOptions are replaced by a consolidated WorkflowJsonOptions that centralizes type-alias registration and adds an explicit AllowLegacyClrTypeNames flag (default enabled, so existing definitions with legacy CLR type names keep loading). Two knock-on changes: incident strategy descriptors at /descriptors/incident-strategies now emit simple CLR type names, and background command dispatch no longer propagates the originating cancellation token, so background commands execute independently. (#7549 · #7499)
POST /workflow-definitions/{definitionId}/publish status code. Previously always returned 200 OK — even when publishing failed on validation errors, in which case it returned 200 with isPublished: false. It now returns 400 Bad Request with the validation messages, mirroring the save-and-publish endpoint. A client that treats 200 as success without inspecting isPublished was silently mis-reporting before and will now see an error; a client that explicitly relied on the 200 needs updating. (#7741)
Refresh tokens can no longer be used as access tokens. Issued JWTs carry a token_use claim, API bearer validation requires access tokens, and /identity/refresh-token has its own scheme. Any client presenting a refresh token to a regular API endpoint will now be rejected. (#7509)
Worth knowing, not breaking
- Identity secret hashing moved to PBKDF2-SHA256 with per-record salts. Legacy SHA-256 hashes remain verifiable and are upgraded in place after a successful validation — no forced password or API-key reset. (#7511)
- Workflow commit transactions are SQL Server EF Core only. Other EF Core providers retain the previous no-op behavior — a deliberate choice to avoid enabling ambient/distributed transactions on providers that may not support them safely. (#7748)
- Bookmark queue purge TTL default increased. If you tuned it explicitly, nothing changes; if you relied on the default, dead-lettered rows linger longer. (#7748)
read:workflow-runtimeis new but not required. ExistingManageWorkflowRuntimegrants keep working on status endpoints. (#7729)- Graceful shutdown is active by default.
UseWorkflowRuntime()registers the drain hosted service unconditionally — there is no enable flag.DrainDeadlinedefaults to 30 seconds, but the effective deadline ismin(DrainDeadline, HostOptions.ShutdownTimeout − 500 ms), so the runtime can never overrun the host's own shutdown budget: with stock .NET defaults (ShutdownTimeout= 30 s) that works out to 29.5 s, leaving a deliberate half-second for persistence to finish. What Elsa cannot see is your orchestrator's grace period. If your KubernetesterminationGracePeriodSecondsis below 30 s, lowerHostOptions.ShutdownTimeoutto match — loweringDrainDeadlinealone is not enough, because the clamp only ever shortens the deadline, never the host timeout. (#7424) - New migrations. The bookmark queue dead-letter store, secrets, external authentication, and AI modules each add EF Core migrations for the providers you use.
- New modules are opt-in. Diagnostics, secrets, dashboards, external authentication, AI, rate limiting, health checks, and the outbox all require explicit registration. Nothing activates by upgrading alone.
Try it
dotnet add package Elsa --version 3.8.0-rc1It's a pre-release, so specify the version explicitly or enable pre-release packages in your IDE.
Road to 3.8.0
The feature set for 3.8.0 is closed; from here we're looking for bugs. The most valuable places to kick the tyres are the ones that changed most:
- Upgrade an existing 3.7.x host and see whether the new security defaults stop you in a way the notes above didn't predict.
- Graceful shutdown under your orchestrator — SIGTERM mid-burst, and the interaction between
DrainDeadlineand your termination grace period. - External authentication against a real IdP.
- Workflow commit behavior on SQL Server, particularly under contention.
Found something? Open an issue and mention 3.8.0-rc1.
Thanks
Four people made their first contribution to Elsa in this release, and every one of them fixed a real, specific bug:
- @jwdb — stopped finished-but-interrupted workflows from restarting forever (#7435)
- @MohitGuptaC — Oracle identifier quoting and
NVARCHAR2casts (#7756) - @DenDeline — request-services restoration after tenant middleware exceptions (#7901)
- @Shivamkmr8 — revert version allocation (#7917)
Thanks also to @RalfvandenBurg for the System.Dynamic variable-accessor fix and @heku for catching a missing [Obsolete] — and to everyone who filed the issues behind these fixes. A large share of this release exists because someone took the time to write up exactly what broke.
Full list of merged pull requests (114)
Note:
Elsa.Http.WebhooksandElsa.Platform.Integrationwere committed directly to the release branch rather than through a pull request, so they don't appear below.
- Fix System.Dynamic registration for C# variable accessors by @RalfvandenBurg in #7415
- fix(csharp): register System.Dynamic for generated Variables wrapper by @sfmskywalker with @Copilot in #7416
- fix: restore HashSet-backed Fork completion state on resumed workflows by @sfmskywalker with @Copilot in #7431
- Graceful shutdown for the workflow runtime (drain, pause, recover) by @sfmskywalker in #7424
- [codex] Add live server log streaming diagnostics by @sfmskywalker in #7438
- [codex] Add structured log SQLite persistence by @sfmskywalker in #7445
- [codex] Expose structured log storage diagnostics by @sfmskywalker in #7446
- [codex] Add structured log provider tests by @sfmskywalker in #7449
- [codex] Increase structured log persistence test coverage by @sfmskywalker in #7450
- [codex] Increase structured log relational test coverage by @sfmskywalker in #7451
- [codex] Add codebase wiki by @sfmskywalker in #7453
- [codex] Add Elsa README video assets by @sfmskywalker in #7455
- [codex] Avoid duplicate structured log storage diagnostics by @sfmskywalker in #7456
- Add missing Obsolete attribute to IWorkflowBuilder by @heku in #7448
- [codex] Resolve build warnings by @sfmskywalker in #7458
- Update safe dependency patch versions by @sfmskywalker in #7459
- [codex] Add wiki update workflow by @sfmskywalker in #7454
- Fix structured log write buffer shutdown flush by @sfmskywalker in #7460
- Fix SQLite structured log shell lifecycle by @sfmskywalker in #7461
- Add state machine activity by @sfmskywalker in #7457
- Add diagnostics console logs by @sfmskywalker in #7462
- [codex] Add package manifest feature metadata by @sfmskywalker in #7463
- fix: do not resume interrupted workflows that are already finished by @jwdb in #7435
- [codex] Update package manifest generator preview by @sfmskywalker in #7465
- [codex] Refresh codebase wiki by @github-actions[bot] in #7464
- [codex] Refresh codebase wiki by @github-actions[bot] in #7466
- Add secrets module by @sfmskywalker in #7468
- [codex] Fix tenant coordinator test options reference by @sfmskywalker in #7503
- [codex] Enforce role assignment authorization by @sfmskywalker in #7501
- Scope HTTP bookmark lookup to tenant by @sfmskywalker in #7508
- Distinguish refresh tokens from API access tokens by @sfmskywalker in #7509
- [codex] Treat Python expressions as privileged host code by @sfmskywalker in #7507
- [codex] Enforce HTTP workflow request body limits while reading by @sfmskywalker in #7497
- [codex] Remove production-usable default admin credentials by @sfmskywalker in #7500
- [codex] Fail fast on default JWT signing keys by @sfmskywalker in #7496
- [codex] Authorize workflow imports before persistence by @sfmskywalker in #7510
- Stabilize bulk dispatch fire-and-forget component test by @sfmskywalker in #7520
- [codex] Require opt-in for localhost authorization grants by @sfmskywalker in #7498
- [codex] Harden C# expression host-code execution by @sfmskywalker in #7519
- Optimize workflow definition sync lookups by @sfmskywalker in #7521
- [codex] Whitelist workflow timestamp filter columns by @sfmskywalker in #7506
- Use cryptographic randomness and adaptive identity secret hashing by @sfmskywalker in #7511
- Add Elsa runtime readiness health checks by @sfmskywalker in #7513
- [codex] Validate distributed runtime lock provider by @sfmskywalker in #7515
- Add bookmark queue dead-letter store by @sfmskywalker in #7516
- Add ingress rate limiting hooks by @sfmskywalker in #7512
- Add workflow dispatch transactional outbox by @sfmskywalker in #7517
- [codex] Harden initial security remediation slice by @sfmskywalker in #7495
- Add OpenTelemetry workflow instrumentation by @sfmskywalker in #7514
- [codex] Harden workflow JSON type resolution by @sfmskywalker in #7499
- [codex] Authorize workflow instance SignalR observation by @sfmskywalker in #7504
- [codex] Secure Resilience simulate response endpoint by @sfmskywalker in #7505
- [codex] Add operational dashboard API PRD by @sfmskywalker in #7529
- [codex] Align console logs hub authorization by @sfmskywalker in #7531
- [codex] Clarify dashboard widget integration contract by @sfmskywalker in #7532
- [codex] Increase Elsa Secrets test coverage by @sfmskywalker in #7530
- [codex] Enforce console logs hub read permission by @sfmskywalker in #7533
- [codex] Refresh codebase wiki by @github-actions[bot] in #7467
- [codex] Scope console logs to workflow instances by @sfmskywalker in #7535
- Enhance console logging with improved context and lifecycle by @sfmskywalker in #7536
- [codex] Increase secrets unit coverage by @sfmskywalker in #7545
- [codex] Fix console log metadata and type resolution by @sfmskywalker in #7542
- [codex] Resolve OpenTelemetry package warnings by @sfmskywalker in #7546
- [codex] Bump dependency security patches by @sfmskywalker in #7547
- [codex] Fix diagnostics live feed regressions by @sfmskywalker in #7548
- Refactor: Overhauls workflow JSON type serialization by @sfmskywalker in #7549
- Optimize activity registry descriptor lookup by @sfmskywalker in #7538
- Preserve activity descriptors on empty refresh by @sfmskywalker in #7550
- [codex] Protect sensitive workflow input descriptors and state by @sfmskywalker in #7573
- [codex] Validate workflow secret references adoption by @sfmskywalker in #7576
- [S2] Add Secret expression runtime by @sfmskywalker in #7574
- [S4] Add JavaScript secret functions by @sfmskywalker in #7575
- [codex] Refresh codebase wiki by @github-actions[bot] in #7534
- [codex] Add EF Core secrets persistence by @sfmskywalker in #7572
- Add operational dashboard API by @sfmskywalker in #7669
- Add Persistence vNext provider-neutral POC by @sfmskywalker in #7680
- Adds operational Dashboard API for Elsa Studio by @sfmskywalker in #7681
- Refactor dashboard API contributors by @sfmskywalker in #7690
- Implement Weaver AI Copilot core by @sfmskywalker in #7523
- [codex] Extract dashboard contributors into companion modules by @sfmskywalker in #7692
- [codex] Add runtime entity validator coverage by @sfmskywalker in #7697
- [codex] Add shell feature manifest categories by @sfmskywalker in #7699
- Use Copilot SDK for Weaver agent loop by @sfmskywalker in #7700
- [codex] Add named WithVariable overload by @sfmskywalker in #7701
- [codex] Fix ForEach completion from nested flowchart by @sfmskywalker in #7702
- Move AI EF Core migrations to provider projects by @sfmskywalker in #7703
- Add Weaver grounding tools by @sfmskywalker in #7704
- Dispose parsed activity JsonDocuments by @sfmskywalker in #7713
- Restore RequestAborted after timed HTTP workflow failures by @sfmskywalker in #7712
- Guard HTTP fault handling when workflow reload returns null by @sfmskywalker in #7714
- [codex] Guard missing NotFoundActivity descriptor during deserialization by @sfmskywalker in #7711
- Add read-only workflow runtime status permission by @sfmskywalker in #7729
- fix: use tenant-agnostic application lookup for API keys by @sfmskywalker in #7705
- Add opt-out for publish-on-validation-error failure by @sfmskywalker in #7741
- Port ASB stable application instance name to 3.8 by @sfmskywalker in #7743
- Forward-port stable ASB application instance names to main by @sfmskywalker in #7744
- Fix scheduling startup backlog catch-up by @sfmskywalker in #7746
- Fix flaky PublishEvent payload assertion by @sfmskywalker in #7752
- Fix PublishEvent payload assertion casing by @sfmskywalker in #7751
- Fix PublishEvent payload assertion casing by @sfmskywalker in #7750
- Fix workflow commit atomicity and bookmark queue retries by @sfmskywalker in #7748
- [codex] Increase AI host test coverage by @sfmskywalker in #7761
- fix: correct Oracle identifier quoting and NVARCHAR2 type cast in GenerateOracleUpsert by @MohitGuptaC in #7756
- chore: update patch dependencies by @sfmskywalker in #7766
- chore: make agent instructions feature-neutral by @sfmskywalker in #7767
- Add extensible external authentication and SSO broker by @sfmskywalker in #7889
- chore: apply safe dependency upgrades by @sfmskywalker in #7896
- Add workflow state rehydration diagnostics by @sfmskywalker in #7899
- Fix tenant service mutation race by @sfmskywalker in #7898
- Avoid repeated tenant-agnostic registry population by @sfmskywalker in #7904
- Enhance external authentication and shadow management by @sfmskywalker in #7903
- Harden external identity race compensation by @sfmskywalker in #7905
- fix: restore request services after tenant middleware exceptions by @DenDeline in #7901
- fix: use last version for revert version allocation by @Shivamkmr8 in #7917
- @jwdb made their first contribution in #7435
- @MohitGuptaC made their first contribution in #7756
- @DenDeline made their first contribution in #7901
- @Shivamkmr8 made their first contribution in #7917
Full Changelog: 3.7.1...3.8.0-rc1