dotnet/orleans · error · KeyNotFoundException

Invalid option {optionId}

Error message

Invalid option {optionId}

What it means

Thrown by Voting PollGrain.AddVote when optionId is outside the valid index range of the poll's Options list (optionId < 0 || optionId >= options.Count). KeyNotFoundException signals that the requested vote option does not exist on this poll.

Source

Thrown at samples/Voting/Grains/PollGrain.cs:30

        [PersistentState(stateName: "pollState", storageName: "votes")]
        IPersistentState<PollState> state) => _votes = state;

    public Task<PollState> GetCurrentResults() => Task.FromResult(_votes.State);

    public async Task CreatePoll(PollState initialState)
    {
        // Set the state and persist it
        _votes.State = initialState;
        await _votes.WriteStateAsync();
    }

    public async Task<PollState> AddVote(int optionId)
    {
        // Perform input validation
        var options = _votes.State.Options;
        if (optionId < 0 || optionId >= options.Count)
        {
            throw new KeyNotFoundException($"Invalid option {optionId}");
        }

        // Add the vote & persist the updated state.
        var (option, votes) = options[optionId];
        options[optionId] = (option, votes + 1);
        await _votes.WriteStateAsync();

        // Notify the watchers.
        _pollWatchers.Notify(watcher => watcher.OnPollUpdated(_votes.State));
        return _votes.State;
    }

    private readonly ObserverManager<IPollWatcher> _pollWatchers = new(TimeSpan.FromMinutes(1));

    public Task StartWatching(IPollWatcher watcher)
    {
        _pollWatchers.Subscribe(watcher);
        return Task.CompletedTask;

View on GitHub (pinned to fca799fa70)

Solutions

  1. Fetch the current PollState (GetCurrentResults) and use its Options.Count to bound optionId before voting.
  2. Validate optionId >= 0 && optionId < options.Length on the client.
  3. Catch KeyNotFoundException at the caller and present 'option no longer available' to the user.

Example fix

// before
await poll.AddVote(optionId); // throws if out of range

// after: validate against the live poll definition
var state = await poll.GetCurrentResults();
if (optionId < 0 || optionId >= state.Options.Count) return;
await poll.AddVote(optionId);
Defensive patterns

Strategy: validation

Validate before calling

// Validate the option id against the current poll before voting
var state = await poll.GetCurrentResults();
if (optionId < 0 || optionId >= state.Options.Count) return;
await poll.AddVote(optionId);

Type guard

static bool IsValidOption(int optionId, PollState state)
    => optionId >= 0 && optionId < state.Options.Count;

Try / catch

null

Prevention

When it happens

Trigger: A client calls pollGrain.AddVote(optionId) with an optionId that is negative or >= the number of options configured when the poll was created.

Common situations: Hard-coding an option id that drifts after the poll is re-created; a client reading a stale poll definition; off-by-one when the client assumes a specific option ordering.

Related errors


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