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
- Replace manual binding construction with the [ImplicitChannelSubscription] or [RegexImplicitChannelSubscription] attribute on the grain class, which always populates all required binding keys.
- If programmatic bindings are necessary, include WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey in the binding dictionary with a valid pattern value (e.g., '*', 'namespace:orders', or 'regex:orders.*').
- 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
- Prefer the [ImplicitChannelSubscription] or [RegexImplicitChannelSubscription] attribute over manual binding construction — it always emits all three required keys.
- If you must build bindings programmatically, write a validation helper that checks for all required keys before registering the binding.
- Review any custom IGrainBindingsProviderAttribute implementations against the GetBindings implementation in ImplicitChannelSubscriptionAttribute to ensure parity.
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
- Channel binding for grain type {binding.GrainType} is missin
- streamId
- Could not find an {nameof(IChannelNamespacePredicate)} for t
- Unknown Playground:Storage:Provider value '{storageProvider}
- WEBSITE_PRIVATE_PORTS must contain at least one TCP port.
AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13).
Data as JSON: /api/errors/85067c7a6866e91d.
Report an issue: GitHub.