Fix CS1026: ) expected in an Oxide plugin (PingKick)
CS1026 means the C# compiler reached the end of a statement while a closing parenthesis was still open. Oxide prints it as "[1] ) expected at <line>:<column>"; add the missing `)` on that line and the plugin compiles again.
Error while compiling PingKick: [1] ) expected at 66:73
Why it happens
Every opening ( in C# must be matched by a closing ). CS1026 fires when the parser finishes a statement, argument list or expression and still finds an unclosed parenthesis, so the token it expects next is ). In Oxide plugins the usual spots are SendReply(player, $"You have {count} seconds (left"); where the text or an interpolation is left open, a for or if condition split over two lines without the closing bracket, timer.Once(5f, () => { ... } where the lambda's ) was dropped, and lang.GetMessage("Key", this, player.UserIDString missing its final bracket. The reported position is where the compiler gave up, not always where the bracket is missing, so scan the lines just above the column it names. Oxide shows the message in the console and writes the full compiler output to oxide/logs; Carbon prints the same CS1026 with its own layout.
How to fix it
- Read the
at <line>:<column>part of the console line and open the plugin at that line in oxide/plugins. - Count opening and closing parentheses in that statement, including inside interpolated strings like
$"...". Look at the line above too: a call split over several lines often has the)on the wrong one. - Pay attention to lambdas passed as callbacks:
timer.Once(3f, () => { ... })needs two closing brackets, one for the lambda body}and one for the method call). - Save the file. Oxide auto-recompiles on save; otherwise run
oxide.reload PingKick. - Confirm success: the console prints the compile error no longer and shows
Loaded plugin PingKick v1.0.0 by RustPluginGenerator(or nothing new if it was already loaded and just reloaded). If a second numbered message appears, fix that one next.
Before and after
using System.Collections.Generic;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("PingKick", "RustPluginGenerator", "1.0.0")]
[Description("Kicks players whose ping stays above a limit for too long")]
public class PingKick : RustPlugin
{
private const string PermAdmin = "pingkick.admin";
private PluginConfig config;
private readonly Dictionary<ulong, float> overLimitSince = new Dictionary<ulong, float>();
private class PluginConfig
{
[Newtonsoft.Json.JsonProperty("Max ping")]
public int MaxPing = 300;
[Newtonsoft.Json.JsonProperty("Seconds above limit before kick")]
public float SecondsBeforeKick = 30f;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new System.Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
private void Init()
{
permission.RegisterPermission(PermAdmin, this);
}
private void OnServerInitialized()
{
timer.Every(5f, CheckPings);
}
private void CheckPings()
{
float now = Time.realtimeSinceStartup;
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsAdmin)
continue;
int ping = Network.Net.sv.GetAveragePing(player.net.connection);
if (ping > config.MaxPing)
{
if (!overLimitSince.ContainsKey(player.userID.Get())
{
overLimitSince[player.userID.Get()] = now;
}
float since = overLimitSince[player.userID.Get()];
if (now - since >= config.SecondsBeforeKick)
{
player.Kick("Ping too high (" + ping + " ms");
overLimitSince.Remove(player.userID.Get());
}
}
else
{
overLimitSince.Remove(player.userID.Get());
}
}
}
private void Unload()
{
overLimitSince.Clear();
}
}
}using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using Oxide.Core;
using UnityEngine;
namespace Oxide.Plugins
{
[Info("PingKick", "RustPluginGenerator", "1.0.0")]
[Description("Kicks players whose ping stays above a limit for too long")]
public class PingKick : RustPlugin
{
private const string PermAdmin = "pingkick.admin";
private PluginConfig config;
private readonly Dictionary<ulong, float> overLimitSince = new Dictionary<ulong, float>();
private class PluginConfig
{
[Newtonsoft.Json.JsonProperty("Max ping")]
public int MaxPing = 300;
[Newtonsoft.Json.JsonProperty("Seconds above limit before kick")]
public float SecondsBeforeKick = 30f;
}
protected override void LoadDefaultConfig() => config = new PluginConfig();
protected override void LoadConfig()
{
base.LoadConfig();
try
{
config = Config.ReadObject<PluginConfig>();
if (config == null) throw new System.Exception();
}
catch
{
PrintWarning("Configuration file is invalid, using defaults");
LoadDefaultConfig();
}
SaveConfig();
}
protected override void SaveConfig() => Config.WriteObject(config);
private void Init()
{
permission.RegisterPermission(PermAdmin, this);
}
private void OnServerInitialized()
{
timer.Every(5f, CheckPings);
}
private void CheckPings()
{
float now = Time.realtimeSinceStartup;
foreach (BasePlayer player in BasePlayer.activePlayerList)
{
if (player == null || !player.IsConnected || player.IsAdmin)
continue;
int ping = Network.Net.sv.GetAveragePing(player.net.connection);
if (ping > config.MaxPing)
{
if (!overLimitSince.ContainsKey(player.userID.Get()))
{
overLimitSince[player.userID.Get()] = now;
}
float since = overLimitSince[player.userID.Get()];
if (now - since >= config.SecondsBeforeKick)
{
player.Kick("Ping too high (" + ping + " ms)");
overLimitSince.Remove(player.userID.Get());
}
}
else
{
overLimitSince.Remove(player.userID.Get());
}
}
}
private void Unload()
{
overLimitSince.Clear();
}
}
}FAQ
The line and column point to code that looks fine. Why?
Roslyn reports the position where it expected ), which is usually the end of the statement after the missing bracket. Start at the reported line and walk backwards through the statement, checking nested calls, for/if conditions and interpolated strings.
Does CS1026 keep my plugin from loading?
Yes. Oxide removes the failing file from the compile batch and does not load it, so commands, hooks and permissions from PingKick are missing until it compiles. Other plugins in oxide/plugins are unaffected.
How is this different from CS1002?
CS1026 is about an unclosed ( or [; CS1002 is a missing ;. Both are syntax errors that stop compilation, and one file can produce either depending on which token the parser wanted when it stopped.
Can I see the full compiler output somewhere?
Yes. Oxide writes the complete Roslyn output, including the source excerpt, to oxide/logs (carbon/logs on Carbon). The console only shows the shortened numbered line, which is why the log is the faster place to look when the column is confusing.
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