elsa-workflows/elsa-core · error · InvalidOperationException
Elsa Platform runtime command poll was not authorized.
Error message
Elsa Platform runtime command poll was not authorized.
What it means
PlatformRuntimeCommandClient.PollAsync GETs the engine's command queue from the Elsa Platform. When the Platform responds 401 or 403, the client converts it into this InvalidOperationException instead of parsing the body, signaling the engine secret or worker identity is not authorized.
Solutions
- Verify EngineSecret matches a valid secret issued by the Platform for this engine ID; update config and restart.
- Confirm EngineId and WorkerId are correct and registered on the Platform for the configured endpoint.
- Check the configured PlatformEndpoint points at the intended environment; re-issue credentials if revoked.
Example fix
// before builder.Services.Configure<ElsaPlatformIntegrationOptions>(o => o.EngineSecret = oldSecret); // after builder.Services.Configure<ElsaPlatformIntegrationOptions>(o => o.EngineSecret = configuration["Elsa:PlatformIntegration:EngineSecret"]!);
Defensive patterns
Strategy: try-catch
Validate before calling
if (string.IsNullOrWhiteSpace(options.EngineSecret) || string.IsNullOrWhiteSpace(options.WorkerId))
throw new InvalidOperationException("EngineSecret and WorkerId must be configured before polling."); Try / catch
try { var commands = await client.PollAsync(ct); }
catch (InvalidOperationException ex) when (ex.Message.Contains("not authorized"))
{
logger.LogError(ex, "Platform rejected command poll credentials; check EngineSecret/WorkerId.");
// back off and alert rather than tight-retrying
} Prevention
- Rotate engine secrets on the Platform and in configuration together.
- Verify the endpoint environment matches the credentials environment.
- Monitor for repeated 401/403 polls and alert on credential drift.
When it happens
Trigger: PollAsync sends a request whose authorization is rejected: missing/invalid lease or auth headers, expired or revoked engine secret, or a WorkerId the Platform does not recognize.
Common situations: Rotated the engine secret on the Platform but not in the runtime configuration; wrong environment (staging secret against production endpoint); clock drift invalidating signed credentials; firewall/proxy stripping the auth header.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Platform runtime command artifact does not include a…
- Elsa Platform endpoint is required.
- Access denied.
- Access denied.
- Access denied.
AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13).
Data as JSON: /api/errors/edf154ba337f55b0.
Report an issue: GitHub.
Appendix: source
Thrown at src/modules/Elsa.Platform.Integration/Services/PlatformRuntimeCommandClient.cs:24
using Elsa.Platform.Integration.Options;
using Microsoft.Extensions.Options;
namespace Elsa.Platform.Integration.Services;
public class PlatformRuntimeCommandClient(HttpClient httpClient, IOptions<ElsaPlatformIntegrationOptions> options) : IPlatformRuntimeCommandClient
{
private const string EngineSecretHeaderName = "X-Elsa-Engine-Secret";
private const string LeaseHeaderName = "X-Elsa-Command-Lease";
private const string WorkerHeaderName = "X-Elsa-Worker-Id";
private static readonly JsonSerializerOptions JsonOptions = CreateJsonOptions();
private readonly ElsaPlatformIntegrationOptions _options = options.Value;
public async Task<IReadOnlyList<PlatformRuntimeCommand>> PollAsync(CancellationToken cancellationToken = default)
{
using var request = CreateRequest(HttpMethod.Get, BuildUri($"/deployments/runtime/engines/{_options.EngineId:D}/commands"));
using var response = await httpClient.SendAsync(request, cancellationToken);
if (response.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
throw new InvalidOperationException("Elsa Platform runtime command poll was not authorized.");
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadFromJsonAsync<PlatformRuntimeCommandListResponse>(JsonOptions, cancellationToken);
return body?.Commands ?? [];
}
public async Task<PlatformRuntimeCommandClaimResponse?> ClaimAsync(Guid commandId, CancellationToken cancellationToken = default)
{
using var response = await SendJsonAsync(
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);View on GitHub (pinned to fe9217bdfa)