{"record":{"id":"35da3bb96d8dbcc9","repo":"dotnet/orleans","slug":"invalid-option-optionid","errorCode":null,"errorMessage":"Invalid option {optionId}","messagePattern":"Invalid option (.+?)","errorType":"exception","errorClass":"KeyNotFoundException","httpStatus":null,"severity":"error","filePath":"samples/Voting/Grains/PollGrain.cs","lineNumber":30,"sourceCode":"        [PersistentState(stateName: \"pollState\", storageName: \"votes\")]\n        IPersistentState<PollState> state) => _votes = state;\n\n    public Task<PollState> GetCurrentResults() => Task.FromResult(_votes.State);\n\n    public async Task CreatePoll(PollState initialState)\n    {\n        // Set the state and persist it\n        _votes.State = initialState;\n        await _votes.WriteStateAsync();\n    }\n\n    public async Task<PollState> AddVote(int optionId)\n    {\n        // Perform input validation\n        var options = _votes.State.Options;\n        if (optionId < 0 || optionId >= options.Count)\n        {\n            throw new KeyNotFoundException($\"Invalid option {optionId}\");\n        }\n\n        // Add the vote & persist the updated state.\n        var (option, votes) = options[optionId];\n        options[optionId] = (option, votes + 1);\n        await _votes.WriteStateAsync();\n\n        // Notify the watchers.\n        _pollWatchers.Notify(watcher => watcher.OnPollUpdated(_votes.State));\n        return _votes.State;\n    }\n\n    private readonly ObserverManager<IPollWatcher> _pollWatchers = new(TimeSpan.FromMinutes(1));\n\n    public Task StartWatching(IPollWatcher watcher)\n    {\n        _pollWatchers.Subscribe(watcher);\n        return Task.CompletedTask;","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/samples/Voting/Grains/PollGrain.cs#L12-L48","documentation":"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.","triggerScenarios":"A client calls pollGrain.AddVote(optionId) with an optionId that is negative or >= the number of options configured when the poll was created.","commonSituations":"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.","solutions":["Fetch the current PollState (GetCurrentResults) and use its Options.Count to bound optionId before voting.","Validate optionId >= 0 && optionId < options.Length on the client.","Catch KeyNotFoundException at the caller and present 'option no longer available' to the user."],"exampleFix":"// before\nawait poll.AddVote(optionId); // throws if out of range\n\n// after: validate against the live poll definition\nvar state = await poll.GetCurrentResults();\nif (optionId < 0 || optionId >= state.Options.Count) return;\nawait poll.AddVote(optionId);","handlingStrategy":"validation","validationCode":"// Validate the option id against the current poll before voting\nvar state = await poll.GetCurrentResults();\nif (optionId < 0 || optionId >= state.Options.Count) return;\nawait poll.AddVote(optionId);","typeGuard":"static bool IsValidOption(int optionId, PollState state)\n    => optionId >= 0 && optionId < state.Options.Count;","tryCatchPattern":"null","preventionTips":["Fetch the live PollState and bound optionId against Options.Count.","Do not hard-code option ids that may drift across poll re-creation.","Catch KeyNotFoundException to show 'option unavailable' to users."],"tags":["voting","validation","index-out-of-range","sample"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}