{"record":{"id":"7448231ad16c7408","repo":"elsa-workflows/elsa-core","slug":"signal-signal-was-not-of-type-typeof-t-name","errorCode":null,"errorMessage":"Signal '{signal}' was not of type '{typeof(T).Name}'.","messagePattern":"Signal '(.+?)' was not of type '(.+?)'\\.","errorType":"exception","errorClass":"InvalidCastException","httpStatus":null,"severity":"error","filePath":"src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs","lineNumber":14,"sourceCode":"﻿using System.Collections.Concurrent;\n\nnamespace Elsa.Testing.Shared.Services;\n\npublic class SignalManager\n{\n    private readonly ConcurrentDictionary<object, TaskCompletionSource<object?>> _signals = new();\n\n    public async Task<T> WaitAsync<T>(object signal, int millisecondsTimeout = 60000)\n    {\n        var result = await WaitAsync(signal, millisecondsTimeout);\n        \n        if(result is not T typedResult)\n            throw new InvalidCastException($\"Signal '{signal}' was not of type '{typeof(T).Name}'.\");\n\n        return typedResult;\n    }\n\n    public async Task<object?> WaitAsync(object signal, int millisecondsTimeout = 60000)\n    {\n        var taskCompletionSource = GetOrCreate(signal);\n        using var cancellationTokenSource = new CancellationTokenSource(millisecondsTimeout);\n        var delayTask = Task.Delay(millisecondsTimeout, cancellationTokenSource.Token);\n        var completedTask = await Task.WhenAny(taskCompletionSource.Task, delayTask);\n\n        if (completedTask == delayTask)\n        {\n            _signals.TryRemove(signal, out _);\n            throw new TimeoutException($\"Signal '{signal}' timed out after {millisecondsTimeout} milliseconds.\");\n        }\n\n        cancellationTokenSource.Cancel();","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/common/Elsa.Testing.Shared.Component/Services/SignalManager.cs#L1-L32","documentation":"Elsa.Testing.Shared's SignalManager stores signals as objects; the generic WaitAsync<T> retrieves the pending signal and throws InvalidCastException when the stored value cannot be cast to T. In test code this means the workflow or component signaled with a different payload type than the test awaited. The non-generic value is discarded and the test fails with this message.","triggerScenarios":"Calling signalManager.WaitAsync<MyPayload>(\"my-signal\") after the workflow triggered the signal with a different payload type (e.g. recorded a string but the test waits for a custom record, or null was signaled); two test threads using the same signal name with different payload types.","commonSituations":"Refactoring a workflow's signal payload type without updating the awaiting test; reusing a signal name across workflow versions with different payload shapes; a race where the timeout path or a default value was signaled instead of the expected typed payload.","solutions":["Make the generic type argument match the type actually passed to TriggerSignal for that signal name.","Inspect what the workflow signals (search for TriggerSignal calls) and align the test's WaitAsync<T> type parameter with it.","Use the non-generic WaitAsync(signal) and log/inspect result.GetType() to discover the actual payload type.","Rename the signal so different payload types don't share one signal key."],"exampleFix":"// before\nvar payload = await signals.WaitAsync<MyEvent>(\"done\"); // workflow signaled a string\n// after\nvar payload = await signals.WaitAsync<string>(\"done\"); // matches actual signal type","handlingStrategy":"type-guard","validationCode":"// verify the expected signal type before asserting\nvar raw = await signals.WaitAsync(\"done\");\nif (raw is not MyEvent)\n    throw new InvalidOperationException($\"Signal 'done' produced {raw?.GetType().Name ?? \"null\"}, expected MyEvent\");","typeGuard":"bool IsValidSignal(object? signal) => signal is MyEvent;\n\nvar result = await signals.WaitAsync(\"done\");\nif (!IsValidSignal(result))\n    Assert.Fail($\"Unexpected signal payload: {result?.GetType().Name}\");","tryCatchPattern":"try\n{\n    var payload = await signals.WaitAsync<MyEvent>(\"done\");\n}\ncatch (InvalidCastException ex)\n{\n    Assert.Fail($\"Signal payload type mismatch: {ex.Message}\");\n}","preventionTips":["Keep signal names and payload types in one shared constants/type file used by both workflow and tests.","Rename signals whenever their payload type changes.","In tests, first use the non-generic WaitAsync to inspect the payload type when a cast fails."],"tags":["testing","signals","type-cast"],"backgroundTag":"type-mismatch","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}