elsa-workflows/elsa-core · error · InvalidOperationException
Platform runtime command artifact does not include a…
Error message
Platform runtime command artifact does not include a download URL.
What it means
DownloadArtifactAsync downloads the payload of a Platform runtime command artifact. The Platform must supply a DownloadUrl on each artifact; when it is missing or blank, the client refuses to build a request and throws this InvalidOperationException.
Solutions
- Inspect the raw command response to confirm whether the Platform actually omitted DownloadUrl; upgrade/align the Platform service if it is an API version mismatch.
- Re-trigger the command on the Platform so the artifact (and its URL) is regenerated.
- Add a guard before calling DownloadArtifactAsync: skip or surface an actionable error when DownloadUrl is null.
Example fix
// before
var bytes = await client.DownloadArtifactAsync(command, artifact, leaseToken);
// after
if (string.IsNullOrWhiteSpace(artifact.DownloadUrl))
throw new InvalidOperationException($"Artifact {artifact.Name} has no download URL; re-issue the command.");
var bytes = await client.DownloadArtifactAsync(command, artifact, leaseToken); Defensive patterns
Strategy: type-guard
Validate before calling
if (string.IsNullOrWhiteSpace(artifact.DownloadUrl))
throw new InvalidOperationException($"Artifact '{artifact.Name}' lacks a download URL; re-issue the command."); Type guard
bool IsDownloadable(PlatformArtifactItem a) => !string.IsNullOrWhiteSpace(a.DownloadUrl);
Try / catch
try { var bytes = await client.DownloadArtifactAsync(command, artifact, leaseToken, ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("download URL"))
{
logger.LogWarning(ex, "Artifact for command {CommandId} has no download URL.", command.Id);
} Prevention
- Keep the runtime and Platform API versions aligned so artifact payloads always include URLs.
- Log the raw command JSON when artifacts look malformed to diagnose server-side issues.
- Handle missing URLs gracefully by re-requesting the command from the Platform.
When it happens
Trigger: Polling returns a command whose artifact has DownloadUrl null/empty/whitespace, then DownloadArtifactAsync is called for that artifact.
Common situations: Platform API version mismatch returning an older artifact shape without download URLs; the artifact upload on the Platform side failed or expired, so the URL was omitted; deserialization silently left the property null due to JSON casing settings.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
- Elsa Platform runtime command poll was not authorized.
- Elsa Platform endpoint is required.
- Content of type is not supported.
- DestinationRejected
- Elsa Platform claim lease duration must be between 1 second…
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/9a49a1cc3c5c5e5f.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Platform.Integration/Services/PlatformRuntimeCommandClient.cs:52
BuildUri($"/deployments/runtime/commands/{commandId:D}/claim"),
new PlatformRuntimeCommandClaimRequest(_options.EngineId, _options.WorkerId, (int)_options.ClaimLeaseDuration.TotalSeconds),
cancellationToken);
if (response.StatusCode == HttpStatusCode.Conflict || response.StatusCode == HttpStatusCode.NotFound)
return null;
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<PlatformRuntimeCommandClaimResponse>(JsonOptions, cancellationToken);
}
public async Task<Stream> DownloadArtifactAsync(
PlatformRuntimeCommand command,
PlatformArtifactItem artifact,
string leaseToken,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(artifact.DownloadUrl))
throw new InvalidOperationException("Platform runtime command artifact does not include a download URL.");
using var request = CreateRequest(HttpMethod.Get, BuildUri(artifact.DownloadUrl));
request.Headers.Add(LeaseHeaderName, leaseToken);
request.Headers.Add(WorkerHeaderName, _options.WorkerId);
using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
var buffer = new MemoryStream();
await CopyBoundedAsync(stream, buffer, cancellationToken);
buffer.Position = 0;
return buffer;
}
public Task ReportProgressAsync(
Guid commandId,
string leaseToken,
string status,
int? percentComplete,View on GitHub (pinned to fe9217bdfa)