elsa-workflows/elsa-core · error · BpmnExportUnavailableException
BpmnErrorCodes.ExportSourceStale
BpmnErrorCodes.ExportSourceStale
Error message
Workflow definition '{definition.DefinitionId}' has changed since it was imported from BPMN: its activity graph no longer matches the graph the stored source was imported against. The BPMN source stored on it no longer corresponds to this definition, so exporting it would silently return a document that is not what this definition currently is. What it means
When a SourceGraphHashCustomPropertyKey is recorded, ResolveSourceXml compares it to a hash of the definition's current activity graph (BpmnContentHash.OfGraph). On mismatch it throws BpmnExportUnavailableException (reason SourceStale): the definition has changed since import, so exporting would return a BPMN document that no longer corresponds to the current definition. This is the graph-hash fast path, preferred over the version check so definitions imported before hashing still export.
Solutions
- Re-import the current workflow state from a fresh BPMN document so the stored source and graph hash are updated
- Export before making further edits, or revert the graph changes so the hash matches again
- Accept the mismatch and regenerate the BPMN from the current model via a different mechanism if a round-trip is not required
Example fix
// before
var xml = bpmnService.Export(id); // stale after designer edits
// after
if (bpmnService.IsSourceStale(id))
await bpmnService.ImportAsync(id, exportCurrentModelAsBpmn()); // refresh stored source
var xml = bpmnService.Export(id); Defensive patterns
Strategy: try-catch
Validate before calling
var hash = definition.CustomProperties.TryGetValue<string>(SourceGraphHashCustomPropertyKey, out var h) ? h : null;
if (hash is not null && hash != BpmnContentHash.OfGraph(definition.StringData)) throw new InvalidOperationException("BPMN source is stale; re-import before export"); Try / catch
try { var xml = service.Export(id); }
catch (BpmnExportUnavailableException ex) when (ex.Reason == BpmnExportUnavailableReason.SourceStale) { await service.ImportAsync(id, freshBpmnForCurrentModel()); } Prevention
- Export before editing the graph after a BPMN import
- Re-import after designer edits if round-tripping is required
- Detect staleness up front by comparing graph hashes before export
When it happens
Trigger: Export on a definition whose SourceGraphHashCustomPropertyKey differs from the hash of definition.StringData — i.e. the activity graph was modified after the BPMN import (activities added/removed/rewired by the designer or another tool).
Common situations: Editing the workflow in the visual designer after importing it from BPMN, then attempting to export it back to BPMN; scripted workflow mutations between import and export.
Related errors
- BpmnErrorCodes.ExportNotImported
- BpmnErrorCodes.ExportSourceVersionUnknown
- 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…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/886c94c88ba820e7.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Bpmn.Interchange/Services/BpmnInterchangeDocumentService.cs:823
throw new BpmnExportUnavailableException(
$"Workflow definition '{definition.DefinitionId}' carries BPMN source, but not the definition version it was recorded against, so "
+ "whether that source still matches this definition cannot be verified. It does not mean this definition was never imported from "
+ "BPMN, and it does not mean the source is stale — there is simply no version recorded to compare against. Re-import the document to "
+ "record a complete, exportable source.",
BpmnExportUnavailableReason.SourceVersionUnknown);
}
// The graph hash, once recorded, is the sole word on staleness: it is unaffected by a metadata-only save
// (a rename, a variable change) that bumps the definition to a new draft version without touching the graph
// the stored source describes, which the version check below would otherwise flag as stale even though the
// document still matches exactly. A definition imported before this marker existed carries no value for it,
// so it falls back to the version check instead of refusing every definition imported under the older
// behaviour.
if (definition.CustomProperties.TryGetValue<string>(SourceGraphHashCustomPropertyKey, out var sourceGraphHash) && !string.IsNullOrEmpty(sourceGraphHash))
{
if (sourceGraphHash != BpmnContentHash.OfGraph(definition.StringData))
{
throw new BpmnExportUnavailableException(
$"Workflow definition '{definition.DefinitionId}' has changed since it was imported from BPMN: its activity graph no longer matches "
+ "the graph the stored source was imported against. The BPMN source stored on it no longer corresponds to this definition, so "
+ "exporting it would silently return a document that is not what this definition currently is.",
BpmnExportUnavailableReason.SourceStale);
}
}
else if (sourceVersion != definition.Version)
{
throw new BpmnExportUnavailableException(
$"Workflow definition '{definition.DefinitionId}' has changed since it was imported from BPMN (imported at version {sourceVersion}, "
+ $"currently at version {definition.Version}). The BPMN source stored on it no longer corresponds to this definition, so exporting it "
+ "would silently return a document that is not what this definition currently is.",
BpmnExportUnavailableReason.SourceStale);
}
return xml;
}
View on GitHub (pinned to fe9217bdfa)