-
-
Notifications
You must be signed in to change notification settings - Fork 579
.pr_agent_auto_best_practices
Pattern 1: Ensure null-safety on optional inputs and object properties, returning safe defaults instead of risking NullReferenceExceptions.
Example code before:
// May NRE if name is null
if (words.Contains(item.Name)) Process(item.Name.Length);
// May NRE due to incorrect condition
if (args != null || args.DelayTime > 0) DoWork(args.DelayTime);
// May throw or propagate exceptions
var url = page.Url;
// May return null leading to later NRE
var instruction = msg?.Text ?? agent?.Description;
Example code after:
if (!string.IsNullOrEmpty(item.Name) && words.Contains(item.Name)) Process(item.Name.Length);
if (args != null && args.DelayTime > 0) DoWork(args.DelayTime);
var url = page?.Url ?? string.Empty;
var instruction = msg?.Text ?? agent?.Description ?? string.Empty;
Relevant past accepted suggestions:
Suggestion 1:
Fix comparer misuse and null checks
Avoid passing a comparer to HashSet.Contains — it only accepts the element. Also, null-check function names to prevent NREs. Build the set with the comparer and call Contains with a single argument, and ensure x.Name is not null before accessing.
src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs [110-124]
public IEnumerable<FunctionDef> FilterFunctions(string instruction, Agent agent, StringComparer? comparer = null)
{
var functions = agent.Functions.AsEnumerable();
if (agent.FuncVisMode.IsEqualTo(AgentFuncVisMode.Auto) && !string.IsNullOrWhiteSpace(instruction))
{
- comparer = comparer ?? StringComparer.OrdinalIgnoreCase;
+ comparer ??= StringComparer.OrdinalIgnoreCase;
var matches = Regex.Matches(instruction, @"\b[A-Za-z0-9_]+\b");
var words = new HashSet<string>(matches.Select(m => m.Value), comparer);
- functions = functions.Where(x => words.Contains(x.Name, comparer));
+ functions = functions.Where(x => !string.IsNullOrEmpty(x.Name) && words.Contains(x.Name));
}
functions = functions.Concat(agent.SecondaryFunctions ?? []);
return functions;
}
Suggestion 2:
Remove unnecessary async and add guard
The method is declared async but contains no await, which can introduce unnecessary state machine overhead and warnings. Also, accessing page.Url may throw if the page is disposed; wrap in a try-catch and return an empty string on failure to align with the null-path behavior.
-public async Task<string> GetCurrentUrl(MessageInfo message)
+public Task<string> GetCurrentUrl(MessageInfo message)
{
- var page = _instance.GetPage(message.ContextId);
- if (page == null)
- return string.Empty;
- return page.Url;
+ try
+ {
+ var page = _instance.GetPage(message.ContextId);
+ if (page == null) return Task.FromResult(string.Empty);
+ return Task.FromResult(page.Url ?? string.Empty);
+ }
+ catch
+ {
+ return Task.FromResult(string.Empty);
+ }
}
Suggestion 3:
Fix null reference bug
The condition has a logical error. It should use AND (&&) instead of OR (||) to ensure both that args is not null and the delay time is positive. The current condition will attempt to access DelayTime even when args is null, causing a NullReferenceException.
src/Infrastructure/BotSharp.Core.Crontab/Functions/TaskWaitFn.cs [25]
-if (args != null || args.DelayTime > 0)
+if (args != null && args.DelayTime > 0)
Suggestion 4:
Prevent null reference exception
The code uses a null conditional operator on agent but doesn't check if agent is null before accessing its Description property. If agent is null, this could lead to a NullReferenceException when trying to access agent.Description.
src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs [283]
-var instruction = messages.FirstOrDefault()?.Content.FirstOrDefault()?.Text ?? agent?.Description;
+var instruction = messages.FirstOrDefault()?.Content.FirstOrDefault()?.Text ?? agent?.Description ?? string.Empty;
Pattern 2: Validate external/system state and asynchronous usage to avoid deadlocks, invalid sends, and unnecessary async overhead.
Example code before:
// Async without await and no guarding
public async Task<string> GetData() { return value; }
// WebSocket receive assumes single frame
var result = await socket.ReceiveAsync(buffer, ct);
var text = Encoding.UTF8.GetString(buffer, 0, result.Count);
// Send without state check
await socket.SendAsync(data, WebSocketMessageType.Text, true, ct);
// Blocking async call
var value = service.GetAsync().Result;
Example code after:
// Remove unnecessary async/await and add guards
public Task<string> GetData() => Task.FromResult(value ?? string.Empty);
// Accumulate frames until EndOfMessage
var bytes = new List<byte>();
WebSocketReceiveResult res;
do
{
res = await socket.ReceiveAsync(buffer, ct);
bytes.AddRange(buffer.AsSpan(0, res.Count).ToArray());
} while (!res.EndOfMessage);
var text = Encoding.UTF8.GetString(bytes.ToArray());
// Check WebSocket state before sending
if (socket.State == WebSocketState.Open)
await socket.SendAsync(data, WebSocketMessageType.Text, true, ct);
// Avoid .Result in async contexts
var value = service.GetAsync().GetAwaiter().GetResult()
Relevant past accepted suggestions:
Suggestion 1:
Remove unnecessary async and add guard
The method is declared async but contains no await, which can introduce unnecessary state machine overhead and warnings. Also, accessing page.Url may throw if the page is disposed; wrap in a try-catch and return an empty string on failure to align with the null-path behavior.
-public async Task<string> GetCurrentUrl(MessageInfo message)
+public Task<string> GetCurrentUrl(MessageInfo message)
{
- var page = _instance.GetPage(message.ContextId);
- if (page == null)
- return string.Empty;
- return page.Url;
+ try
+ {
+ var page = _instance.GetPage(message.ContextId);
+ if (page == null) return Task.FromResult(string.Empty);
+ return Task.FromResult(page.Url ?? string.Empty);
+ }
+ catch
+ {
+ return Task.FromResult(string.Empty);
+ }
}
Suggestion 2:
Fix WebSocket fragmentation handling
The code is using a fixed-size buffer for receiving WebSocket messages, but it doesn't handle fragmented messages correctly. WebSocket messages can be split across multiple frames, and the current implementation will process each frame independently, potentially causing data corruption for large messages.
src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs [61-77]
var buffer = new byte[1024 * 32];
WebSocketReceiveResult result;
+var messageBuffer = new List<byte>();
do
{
result = await webSocket.ReceiveAsync(new(buffer), CancellationToken.None);
if (result.MessageType != WebSocketMessageType.Text)
{
continue;
}
- var receivedText = Encoding.UTF8.GetString(buffer, 0, result.Count);
- if (string.IsNullOrEmpty(receivedText))
+ messageBuffer.AddRange(new ArraySegment<byte>(buffer, 0, result.Count));
+
+ if (result.EndOfMessage)
{
- continue;
- }
+ var receivedText = Encoding.UTF8.GetString(messageBuffer.ToArray());
+ messageBuffer.Clear();
+
+ if (string.IsNullOrEmpty(receivedText))
+ {
+ continue;
+ }
Suggestion 3:
Check WebSocket state
The SendEventToUser method doesn't check if the WebSocket is in a valid state before sending data. If the client has disconnected or the connection is closing, this could throw an exception and crash the middleware.
src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs [106]
-await SendEventToUser(webSocket, data);
+if (webSocket.State == WebSocketState.Open)
+{
+ await SendEventToUser(webSocket, data);
+}
[Suggestion has been applied]
Suggestion 4:
Avoid potential deadlocks
The code is using .Result to synchronously wait for an asynchronous operation, which can lead to deadlocks in ASP.NET applications. This is a common anti-pattern that should be avoided.
src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs [85-93]
// Convert id to name
if (!Guid.TryParse(agentId, out _))
{
var agentService = _services.GetRequiredService<IAgentService>();
- var agents = agentService.GetAgentOptions([agentId]).Result;
+ var agents = agentService.GetAgentOptions([agentId]).GetAwaiter().GetResult();
if (agents.Count > 0)
{
agentId = agents.First().Id;
[Suggestion has been applied]
[Auto-generated best practices - 2025-09-11]