github elsa-workflows/elsa-core 3.8.0-rc1

3 hours ago

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:

  • IQuiescenceSignal composes two flags — Drain (forward-only, triggered by host stop) and AdministrativePause (reversible, operator-triggered). Both are idempotent; pause can optionally persist via IKeyValueStore so it survives a restart.
  • IIngressSource is 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 to IForceStoppable where available. A source that claims Paused and then starts a burst anyway is detected and flipped to PauseFailed rather than trusted.
  • BurstTrackingMiddleware registers 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 as Interrupted, and a WorkflowInterrupted log entry is written.
  • Recovery on next activation scans for Interrupted instances — a scan deliberately disjoint from the existing timeout-based RestartInterruptedWorkflowsTask, 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.ApiGET /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 logsElsa.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 Secret expression stores a SecretReference, not a resolved value, and resolves through ISecretResolver at execution time — so definitions round-trip through JSON without ever carrying the secret (#7574).
  • getSecret(name) is available in JavaScript expressions via Jint, typed as Promise<string>, composable with .then or an async IIFE (#7575).
  • Sensitive inputs are excluded from persisted state. InputAttribute.CanContainSecrets now maps to InputDescriptor.IsSensitive, flows through to the API client model for Studio, and evaluated sensitive values are actively removed from ActivityState rather than merely skipped (#7573).
  • Adoption was validated end to end through the HTTP Request Authorization header 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.

#7889 · #7903 · #7905


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.

#7523 · #7700 · #7703 · #7704


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 stays true; errors remain on result.ValidationErrors. See Upgrade notes — this one also changes an HTTP status code. (#7741)
  • A missing [Obsolete] on IWorkflowBuilder (#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-runtime is new but not required. Existing ManageWorkflowRuntime grants keep working on status endpoints. (#7729)
  • Graceful shutdown is active by default. UseWorkflowRuntime() registers the drain hosted service unconditionally — there is no enable flag. DrainDeadline defaults to 30 seconds, but the effective deadline is min(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 Kubernetes terminationGracePeriodSeconds is below 30 s, lower HostOptions.ShutdownTimeout to match — lowering DrainDeadline alone 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-rc1

It'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 DrainDeadline and 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 NVARCHAR2 casts (#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.Webhooks and Elsa.Platform.Integration were committed directly to the release branch rather than through a pull request, so they don't appear below.

Full Changelog: 3.7.1...3.8.0-rc1

Don't miss a new elsa-core release

NewReleases is sending notifications on new releases.