dotnet/orleans · error · KeyNotFoundException

Channel binding for grain type {binding.GrainType} is missin

Error message

Channel binding for grain type {binding.GrainType} is missing a "{WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey}" value

What it means

Thrown during ImplicitChannelSubscriberTable.BuildCache when a grain binding dictionary is marked as type 'broadcast-channel' (via WellKnownGrainTypeProperties.BindingTypeKey) but does not contain the 'channel-pattern' key (WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey). The pattern specifies which namespace predicate to use for matching published messages to subscribers; without it the binding is malformed. Under normal usage via [ImplicitChannelSubscription], this key is always populated from the predicate's PredicatePattern.

Source

Thrown at src/Orleans.BroadcastChannel/SubscriberTable/ImplicitChannelSubscriberTable.cs:77

        }

        private Cache BuildCache(MajorMinorVersion version, ImmutableDictionary<GrainType, GrainBindings> bindings)
        {
            var newPredicates = new List<BroadcastChannelSubscriberPredicate>();

            foreach (var binding in bindings.Values)
            {
                foreach (var grainBinding in binding.Bindings)
                {
                    if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type)
                        || type != WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue)
                    {
                        continue;
                    }

                    if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey, out var pattern))
                    {
                        throw new KeyNotFoundException(
                           $"Channel binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey}\" value");
                    }

                    IChannelNamespacePredicate? predicate = null;
                    foreach (var provider in _providers)
                    {
                        if (provider.TryGetPredicate(pattern, out predicate)) break;
                    }

                    if (predicate is null)
                    {
                        throw new KeyNotFoundException(
                            $"Could not find an {nameof(IChannelNamespacePredicate)} for the pattern \"{pattern}\"."
                            + $" Ensure that a corresponding {nameof(IChannelNamespacePredicateProvider)} is registered");
                    }

                    if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.ChannelIdMapperKey, out var mapperName))
                    {

View on GitHub (pinned to fca799fa70)

Solutions

  1. Replace manual binding construction with the [ImplicitChannelSubscription] or [RegexImplicitChannelSubscription] attribute on the grain class, which always populates all required binding keys.
  2. If programmatic bindings are necessary, include WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey in the binding dictionary with a valid pattern value (e.g., '*', 'namespace:orders', or 'regex:orders.*').
  3. Audit any custom IGrainBindingsProviderAttribute implementations to ensure they emit all three required keys: type, channel-pattern, and channelid-mapper.

Example fix

// before — manual binding missing channel-pattern
var binding = new Dictionary<string, string?>
{
    [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue,
    // missing channel-pattern
};

// after — all required keys present
var binding = new Dictionary<string, string?>
{
    [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue,
    [WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey] = "namespace:orders",
    [WellKnownGrainTypeProperties.ChannelIdMapperKey] = DefaultChannelIdMapper.Name,
};
Defensive patterns

Strategy: validation

Validate before calling

// Validate a manually constructed broadcast-channel binding has all required keys.
static void ValidateBroadcastChannelBinding(Dictionary<string, string?> binding)
{
    if (binding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type)
        && type == WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue)
    {
        if (!binding.ContainsKey(WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey))
            throw new InvalidOperationException(
                "Broadcast-channel binding is missing the required 'channel-pattern' key.");
        if (!binding.ContainsKey(WellKnownGrainTypeProperties.ChannelIdMapperKey))
            throw new InvalidOperationException(
                "Broadcast-channel binding is missing the required 'channelid-mapper' key.");
    }
}

Prevention

When it happens

Trigger: Grain bindings are constructed programmatically or via a custom IGrainBindingsProviderAttribute that sets BindingTypeKey to 'broadcast-channel' but omits BroadcastChannelBindingPatternKey. This occurs at silo startup when the ImplicitChannelSubscriberTable constructor calls BuildCache to build its subscriber cache from all grain bindings. It never occurs when using the built-in [ImplicitChannelSubscription] or [RegexImplicitChannelSubscription] attributes, since their GetBindings method always emits the pattern key.

Common situations: A custom grain binding provider or programmatic manifest generator copies only the type field when constructing broadcast-channel bindings. Migrating from Orleans.Streams implicit stream subscriptions to BroadcastChannel and copying binding dictionaries without translating 'pattern' to 'channel-pattern'. Reflection-based or convention-based grain registration that builds binding dictionaries manually and forgets the pattern entry.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/85067c7a6866e91d. Report an issue: GitHub.