elsa-workflows/elsa-core · error · TimeoutException

Signal ' ' timed out after milliseconds.

Error message

Signal '{signal}' timed out after {millisecondsTimeout} milliseconds.

What it means

SignalManager.WaitAsync waits on a TaskCompletionSource for the signal while a Task.Delay races against it; when the delay wins, the pending signal entry is removed and a TimeoutException is thrown. This means the awaited signal was never triggered (or was triggered before the wait started / with a different name) within the configured timeout (default 60000 ms).

Solutions

  1. Verify the workflow actually calls TriggerSignal with the exact same signal name (check spelling and casing).
  2. Ensure WaitAsync is called before or concurrently with the workflow run so the TCS is registered before the signal fires.
  3. Increase the millisecondsTimeout argument (or default 60000) when workflows are slow, especially in CI.
  4. Check workflow execution logs for faults that prevent the signal activity from running; ensure a fresh SignalManager per test to avoid cross-test signal leakage.

Example fix

// before
var result = await signals.WaitAsync<string>("approved"); // times out at 60s
// after
await workflowClient.RunWorkflowAsync(...); // ensure run actually reaches TriggerSignal("approved")
var result = await signals.WaitAsync<string>("approved", 120000);
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm the workflow will signal before waiting
Assert.True(workflowDefinition.Activities.Any(a => a.Type == "Signal" && a.GetSignalName() == "approved"),
    "Workflow does not contain an activity that triggers signal 'approved'.");

Try / catch

try
{
    var result = await signals.WaitAsync<string>("approved", 120000);
}
catch (TimeoutException ex)
{
    Assert.Fail($"Signal never arrived: {ex.Message}. Check workflow faulted state and signal name.");
}

Prevention

When it happens

Trigger: Calling signalManager.WaitAsync("signal-name") when no code calls TriggerSignal("signal-name") within the timeout; the signal was triggered before WaitAsync registered its TCS; workflow failed or never reached the signaling activity; timeout parameter set too low for a slow workflow.

Common situations: The workflow under test throws or blocks before reaching the signal; a typo or case mismatch between signal names in trigger and wait; parallel tests sharing one SignalManager steal each other's signals; CI machines are slower than the hard-coded timeout.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/7a719f8f714df349. Report an issue: GitHub.

Appendix: source

Thrown at src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs:29

        var result = await WaitAsync(signal, millisecondsTimeout);
        
        if(result is not T typedResult)
            throw new InvalidCastException($"Signal '{signal}' was not of type '{typeof(T).Name}'.");

        return typedResult;
    }

    public async Task<object?> WaitAsync(object signal, int millisecondsTimeout = 60000)
    {
        var taskCompletionSource = GetOrCreate(signal);
        using var cancellationTokenSource = new CancellationTokenSource(millisecondsTimeout);
        var delayTask = Task.Delay(millisecondsTimeout, cancellationTokenSource.Token);
        var completedTask = await Task.WhenAny(taskCompletionSource.Task, delayTask);

        if (completedTask == delayTask)
        {
            _signals.TryRemove(signal, out _);
            throw new TimeoutException($"Signal '{signal}' timed out after {millisecondsTimeout} milliseconds.");
        }

        cancellationTokenSource.Cancel();
        _signals.TryRemove(signal, out _);
        return await taskCompletionSource.Task;
    }

    public void Trigger(object signal, object? result = null)
    {
        var taskCompletionSource = GetOrCreate(signal);

        if (taskCompletionSource.Task.IsCompleted)
            return;

        taskCompletionSource.SetResult(result);
    }

    private TaskCompletionSource<object?> GetOrCreate(object eventName)

View on GitHub (pinned to fe9217bdfa)