mRemoteNG/mRemoteNG · error · InvalidOperationException
An option with key '{option.Key}' already exists.
Error message
An option with key '{option.Key}' already exists. What it means
OptionsStore.AddOptionAsync throws InvalidOperationException when OptionKeyExists returns true for the given key. The options table enforces a UNIQUE constraint on key; this explicit pre-check provides a clearer, domain-specific message than the raw SQLite constraint violation. It indicates a duplicate insert attempt, not a malformed argument.
Source
Thrown at mRemoteNG/Config/Settings/Store/OptionsStore.cs:238
/// <summary>
/// Adds a new option to the store.
/// </summary>
public async Task<OptionInfo> AddOptionAsync(OptionInfo option)
{
if (option == null)
throw new ArgumentNullException(nameof(option));
if (string.IsNullOrWhiteSpace(option.Key))
throw new ArgumentException("Option key cannot be null or whitespace.", nameof(option));
EnsureReady();
return await Task.Run(() =>
{
// Check if option key already exists
if (OptionKeyExists(option.Key))
{
throw new InvalidOperationException($"An option with key '{option.Key}' already exists.");
}
const string sql = """
INSERT INTO options (key, value, category, description, option_type, created_at, modified_at)
VALUES (@key, @value, @category, @description, @option_type, @created_at, @modified_at);
SELECT last_insert_rowid();
""";
using SqliteCommand cmd = _connection.CreateCommand();
cmd.CommandText = sql;
cmd.Parameters.AddWithValue("@key", option.Key);
cmd.Parameters.AddWithValue("@value", option.Value ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@category", option.Category ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@description", option.Description ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@option_type", option.OptionType ?? "string");
cmd.Parameters.AddWithValue("@created_at", DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
cmd.Parameters.AddWithValue("@modified_at", DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
View on GitHub (pinned to 9211babf35)
Solutions
- Call OptionExistsAsync(key) first and branch to UpdateOptionAsync when it returns true (upsert pattern).
- Catch InvalidOperationException and fall back to GetOptionByKeyAsync + UpdateOptionAsync.
- Clear or deduplicate data before bulk import.
- For concurrent access, serialize writes through a lock or single writer task — the check-then-insert is not atomic.
Example fix
// before
await store.AddOptionAsync(opt); // throws if key exists
// after
if (await store.OptionExistsAsync(opt.Key))
{
var existing = await store.GetOptionByKeyAsync(opt.Key);
opt.Id = existing.Id;
await store.UpdateOptionAsync(opt);
}
else
{
await store.AddOptionAsync(opt);
} Defensive patterns
Strategy: validation
Validate before calling
if (await store.OptionExistsAsync(option.Key))
{
var existing = await store.GetOptionByKeyAsync(option.Key);
option.Id = existing.Id;
await store.UpdateOptionAsync(option);
}
else
{
await store.AddOptionAsync(option);
} Try / catch
try { await store.AddOptionAsync(option); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already exists"))
{ /* fall back to update — see validationCode */ } Prevention
- Implement an upsert helper that checks existence before insert.
- For concurrent writers, serialize access with a lock — the check-then-insert is not atomic.
- Catch InvalidOperationException specifically, not the base Exception.
When it happens
Trigger: Calling AddOptionAsync twice with the same Key; importing options that already exist; a check-then-insert sequence where another caller inserted the key between the OptionKeyExists check and the INSERT (the check and insert are not atomic — no transaction or lock wraps them).
Common situations: Re-running a data-seeding routine without clearing first; importing a schema export on top of existing data; concurrent calls from multiple threads/tasks hitting the same SQLite connection.
Related errors
- An option with key '{option.Key}' already exists.
- Option key cannot be null or whitespace.
- Schema SQL cannot be null or whitespace.
- Key cannot be null or whitespace.
- ID must be greater than 0.
AI-assisted analysis of mRemoteNG/mRemoteNG@9211babf35 (2026-08-13).
Data as JSON: /api/errors/42cbd028d2f62df1.
Report an issue: GitHub.