elsa-workflows/elsa-core · error · InvalidOperationException
BPMN element ' ' binds process activity ' ' as the work of…
Error message
BPMN element '{start.ElementId}' binds process activity '{nested.Id}' as the work of scope '{process.Id}', but that activity declares itself the workflow's root scope. A nested scope's start events are internal to the process around it, not workflow entry points, so it must not be marked as able to start the workflow. What it means
BpmnCommandApplier.ApplyAsync pre-scans StartWork commands and refuses a command that binds a process activity marked as the workflow's root scope (IsRootScope) as nested work of another scope. Nested scopes' start events are internal and must never act as workflow entry points. It throws InvalidOperationException naming the BPMN element, the bound activity, and the owning scope.
Solutions
- Fix the model so the referenced process is not marked as root scope but is genuinely nested.
- Regenerate the command batch so StartWork only binds non-root child activities.
- Check the version alignment between the interpreter producing commands and the host applying them.
Defensive patterns
Strategy: try-catch
Validate before calling
foreach (var c in commands.OfType<BpmnHostCommand.StartWork>())
if (process.FindWorkActivity(c.BindingRef) is BpmnProcess { IsRootScope: true }) throw new InvalidOperationException("Batch binds a root scope as nested work."); Try / catch
try { await applier.ApplyAsync(context, commands); }
catch (InvalidOperationException ex) when (ex.Message.Contains("root scope")) { log.LogError(ex, "Invalid StartWork binding"); throw; } Prevention
- Keep model nesting consistent: root processes are never bound as children.
- Regenerate command batches after model edits.
- Pin interpreter and host to the same version.
When it happens
Trigger: Applying a host command batch (via ApplyAsync) containing BpmnHostCommand.StartWork whose BindingRef resolves to a BpmnProcess with IsRootScope == true while nested under another process.
Common situations: Corrupted or hand-built command batches; a bug in the interpreter's binding derivation; model where a root process is incorrectly referenced as a subprocess of another process.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- An < : > element declares no ' ', so there is nothing to…
- An < : > element of the ' ' binding declares no ' '.
- The ' ' binding declares the input ' ' more than once. Each…
- The binding to activity type
- The binding names activity type
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/ad4f7b51e2207f89.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Bpmn/Hosting/BpmnCommandApplier.cs:44
/// Applies a command list <b>in the order returned</b>.
/// </summary>
/// <remarks>
/// The ordering carries meaning and is not an implementation detail. An interrupting boundary event emits the
/// boundary path's <c>StartWork</c> <i>before</i> the teardown that retires the host it interrupted, and a host
/// that tidied up first would be applying a different process.
/// </remarks>
public async ValueTask ApplyAsync(IReadOnlyList<BpmnHostCommand> commands)
{
// Refused before anything in the batch is applied. Applying commands one at a time and refusing only once
// the offending StartWork is reached would leave earlier commands in the same batch already applied and
// saved via memory.SaveWork() below — and under ContinueWithIncidentsStrategy that throw is absorbed into
// an incident rather than surfaced, so the workflow would carry on with a half-applied batch and half-saved
// memory instead of the refusal stopping it clean.
foreach (var start in commands.OfType<BpmnHostCommand.StartWork>())
{
if (process.FindWorkActivity(start.BindingRef) is BpmnProcess { IsRootScope: true } nested)
{
throw new InvalidOperationException(
$"BPMN element '{start.ElementId}' binds process activity '{nested.Id}' as the work of scope '{process.Id}', but that activity declares itself the workflow's root scope. "
+ "A nested scope's start events are internal to the process around it, not workflow entry points, so it must not be marked as able to start the workflow.");
}
}
foreach (var command in commands)
{
switch (command)
{
case BpmnHostCommand.StartWork start:
await StartWorkAsync(start);
break;
case BpmnHostCommand.CancelWorkSubtree cancel:
await CancelWorkSubtreeAsync(cancel);
break;
case BpmnHostCommand.SignalEnclosingScope signal:
await SignalEnclosingScopeAsync(signal);
break;View on GitHub (pinned to fe9217bdfa)