Fix ArgumentNullException: Value cannot be null. Parameter name: key (OnPlayerChat)
A Dictionary lookup was performed with a null string key. In an Oxide plugin this usually means a player id, name or clan tag resolved to null and was passed straight into a dictionary indexer or TryGetValue. Guard the key before the lookup and fall back to a default.
Failed to call hook 'OnPlayerChat' on plugin 'TeamTags v1.0.0' (ArgumentNullException: Value cannot be null. Parameter name: key) at System.Collections.Generic.Dictionary`2[TKey,TValue].FindEntry (TKey key) [0x00008] in <b0e1ad7573a24fd5a9f2af9595e677e7>:0
Why it happens
Dictionary<TKey,TValue>.FindEntry is the internal method behind the indexer (dict[key]) and TryGetValue. .NET throws ArgumentNullException with Parameter name: key as soon as the key argument is null, before any comparison happens. In Rust plugins the null key almost always comes from data outside your control: a player whose displayName is empty or not yet set, a missing config entry read back with Config["SomeKey"].ToString() where the value is absent, a clan or tag string fetched from another plugin through Call that returned null, or a BasePlayer field captured after the player disconnected. OnPlayerChat fires on every chat line, so one bad entry makes the exception repeat on every message. The plugin stays loaded and the hook keeps being called, which fills the console with identical stack traces. Carbon surfaces the same exception with its own prefix, but the underlying .NET behavior is identical.
How to fix it
- Open the plugin and find every dictionary read inside
OnPlayerChat(indexer orTryGetValue). - Print the value you are about to use as a key:
Puts($"chat key = [{key ?? "<null>"}]");. Reload withoxide.reload TeamTagsand type in chat. The line that shows<null>is your key. - Fix the source, not the crash: trim and validate the string before it becomes a key (
string.IsNullOrEmpty(key)), and return early from the hook if it is still empty. - Replace
dict[key]withdict.TryGetValue(key, out var value); it still needs a non-null key, so keep the null check first. - Confirm by feeding the same input again; the console should log no
Failed to call hook 'OnPlayerChat'block. Remove the temporaryPutsline afterwards so you do not spam the log.
Before and after
using System.Collections.Generic;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("TeamTags", "RustPluginGenerator", "1.0.0")]
[Description("Prefixes chat messages with the player's team tag")]
public class TeamTags : RustPlugin
{
private readonly Dictionary<string, string> tags = new Dictionary<string, string>();
private void OnServerInitialized()
{
tags["76561198000000000"] = "ALPHA";
}
private object OnPlayerChat(BasePlayer player, string message, Chat.ChatChannel channel)
{
// player is not null-checked, and the lookup uses a raw key
string tag = tags[player.UserIDString];
if (tag == null) return null;
Server.Broadcast($"[{tag}] {player.displayName}: {message}");
return true;
}
private object OnPlayerDisconnected(BasePlayer player)
{
return null;
}
}
}using System.Collections.Generic;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("TeamTags", "RustPluginGenerator", "1.0.0")]
[Description("Prefixes chat messages with the player's team tag")]
public class TeamTags : RustPlugin
{
private readonly Dictionary<string, string> tags = new Dictionary<string, string>();
private void OnServerInitialized()
{
tags["76561198000000000"] = "ALPHA";
}
private object OnPlayerChat(BasePlayer player, string message, ConVar.Chat.ChatChannel channel)
{
if (player == null) return null;
string userId = player.UserIDString;
if (string.IsNullOrEmpty(userId)) return null;
string tag;
tags.TryGetValue(userId, out tag);
if (!string.IsNullOrEmpty(tag))
{
Server.Broadcast($"[{tag}] {player.displayName}: {message}");
return true;
}
return null;
}
private object OnPlayerDisconnected(BasePlayer player)
{
if (player == null) return null;
string userId = player.UserIDString;
if (!string.IsNullOrEmpty(userId)) tags.Remove(userId);
return null;
}
}
}FAQ
Does Dictionary.TryGetValue throw on a null key?
Yes. TryGetValue avoids the KeyNotFoundException, not the ArgumentNullException. A null key is rejected before lookup in both the indexer and TryGetValue, so you must check key != null yourself.
Why does the error repeat every time someone types?
Oxide catches the exception, prints Failed to call hook ... and leaves the plugin loaded. The hook is still registered, so the next chat message runs the same code and throws again. Fixing the null key stops the repeated block.
The stack trace only shows System.Collections.Generic, not my class.
Release builds of the .NET Framework runtime in Rust often show only the framework frame. The name after on plugin and the parameter name key still point at a dictionary read in that plugin. Adding a temporary Puts before each lookup finds the line.
Can a value from another plugin be the null key?
Yes. [PluginReference] calls return null when the other plugin is absent or when it returns nothing. Always assign the result to a local, check it for null and non-empty, then use it as a key.
Different error?
Paste the full console output and get the cause and a fix in plain English.
Open the error explainerSkip the debugging
Generated plugins are compiled before you get them, and compiler errors are fixed automatically.
Generate a plugin2 free plugins a month, no card.
Last updated Oct 26, 2026.
Get plugins that compile first time
Generate