dotnet/yarp · error · InvalidOperationException

IProxyConfig.ChangeToken has a null value.

Error message

IProxyConfig.ChangeToken has a null value.

What it means

After confirming `GetConfig()` returned non-null, YARP validates that `IProxyConfig.ChangeToken` is not null. The change token is essential because `ProxyConfigManager` uses it to detect config changes and trigger hot reloads. A null change token means YARP can never detect updates, so it refuses to start with this configuration.

Source

Thrown at src/ReverseProxy/Management/ProxyConfigManager.cs:337

            Log.ErrorReloadingConfig(_logger, ex);

            foreach (var configChangeListener in _configChangeListeners)
            {
                configChangeListener.ConfigurationLoadingFailed(instance.Provider, ex);
            }
        }
    }

    private static void ValidateConfigProperties(IProxyConfig config)
    {
        if (config is null)
        {
            throw new InvalidOperationException($"{nameof(IProxyConfigProvider.GetConfig)} returned a null value.");
        }

        if (config.ChangeToken is null)
        {
            throw new InvalidOperationException($"{nameof(IProxyConfig.ChangeToken)} has a null value.");
        }
    }

    private ValueTask<IProxyConfig> LoadConfigAsync(IProxyConfigProvider provider, CancellationToken cancellationToken)
    {
        var config = provider.GetConfig();
        ValidateConfigProperties(config);

        if (_destinationResolver.GetType() == typeof(NoOpDestinationResolver))
        {
            return new(config);
        }

        return LoadConfigAsyncCore(config, cancellationToken);
    }

    private async ValueTask<IProxyConfig> LoadConfigAsyncCore(IProxyConfig config, CancellationToken cancellationToken)
    {

View on GitHub (pinned to bd11867bee)

Solutions

  1. In the custom `IProxyConfig` implementation, always provide a valid `IChangeToken` for `ChangeToken` — use a `CancellationChangeToken` backed by a `CancellationTokenSource` for reload support.
  2. If reloads are not needed, return `NullChangeToken.Singleton` (or a new `CancellationChangeToken(new CancellationTokenSource().Token)`) as a no-op token.
  3. Use the built-in `InMemoryConfigProvider` as a template — it manages a `CancellationTokenSource` and exposes it as the change token.
  4. Add a constructor or factory that always initializes the `ChangeToken` property, preventing null at the source.

Example fix

// before — ChangeToken left null
public class MyProxyConfig : IProxyConfig
{
    public IReadOnlyList<RouteConfig> Routes { get; set; }
    public IReadOnlyList<ClusterConfig> Clusters { get; set; }
    public IChangeToken ChangeToken { get; set; } // null!
}
// after — always provide a valid change token
public class MyProxyConfig : IProxyConfig
{
    private readonly CancellationTokenSource _cts = new();
    public IReadOnlyList<RouteConfig> Routes { get; init; } = Array.Empty<RouteConfig>();
    public IReadOnlyList<ClusterConfig> Clusters { get; init; } = Array.Empty<ClusterConfig>();
    public IChangeToken ChangeToken => new CancellationChangeToken(_cts.Token);
    internal void TriggerReload() { _cts.Cancel(); }
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate ChangeToken before returning config
public IProxyConfig GetConfig()
{
    var config = BuildConfig();
    if (config?.ChangeToken is null)
        throw new InvalidOperationException("ProxyConfig must have a non-null ChangeToken");
    return config;
}

Type guard

static bool HasValidChangeToken(IProxyConfig? config)
    => config?.ChangeToken is not null;

Try / catch

// Not applicable — fix the IProxyConfig implementation to always provide a ChangeToken.

Prevention

When it happens

Trigger: A custom `IProxyConfig` implementation (or a custom `IProxyConfigProvider` that constructs one) does not initialize the `ChangeToken` property. During `ValidateConfigProperties` (line 335), the null check fails and throws. The exception propagates through `LoadConfigAsync` and up through the `InitialLoadAsync` wrapper.

Common situations: A developer creates a custom `IProxyConfig` class and forgets to set the `ChangeToken` property. A provider manually constructs config objects without using the built-in `ConfigurationConfigProvider` or `InMemoryConfigProvider` which handle this correctly. A config reload path creates a new config object but doesn't carry over the change token.

Related errors


AI-assisted analysis of dotnet/yarp@bd11867bee (2026-08-13). Data as JSON: /api/errors/b8f637290958950d. Report an issue: GitHub.