From c06883b9763375d926574eecc23248844ef9dab3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:17:57 +0000 Subject: [PATCH 1/2] Initial plan From ffb46f96256c5875fb00fd22940589d493be0258 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 19:23:34 +0000 Subject: [PATCH 2/2] Fix null ThreadId crash for short meeting URL joins in EchoBot BotService For short Teams meeting URLs, JoinInfo.ParseJoinURL returns an empty ChatInfo with a null ThreadId. This caused an ArgumentNullException in JoinCallAsync (used as ConcurrentDictionary key) and potential NullReferenceExceptions in CallsOnUpdated. Changes to BotService.cs: - JoinCallAsync: only perform duplicate-call check when ThreadId is non-null/empty; short URL joins proceed directly to AddAsync - CallsOnUpdated: use null-conditional ChatInfo access and fall back to call.Id when ThreadId is absent, ensuring handlers are always stored and removed with a valid non-null key Co-authored-by: samarthasthana <3836631+samarthasthana@users.noreply.github.com> --- .../EchoBot/src/EchoBot/Bot/BotService.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs b/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs index 61f59fad..c568ae7e 100644 --- a/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs +++ b/Samples/PublicSamples/EchoBot/src/EchoBot/Bot/BotService.cs @@ -217,15 +217,18 @@ public async Task JoinCallAsync(JoinCallBody joinCallBody) }; } - if (!this.CallHandlers.TryGetValue(joinParams.ChatInfo.ThreadId, out CallHandler? call)) + // For short meeting URL joins, ChatInfo.ThreadId is not known until after the call + // is established, so skip duplicate detection when ThreadId is null or empty. + var threadId = joinParams.ChatInfo.ThreadId; + if (!string.IsNullOrEmpty(threadId) && this.CallHandlers.TryGetValue(threadId, out CallHandler? _)) { - var statefulCall = await this.Client.Calls().AddAsync(joinParams, scenarioId).ConfigureAwait(false); - statefulCall.GraphLogger.Info($"Call creation complete: {statefulCall.Id}"); - _logger.LogInformation($"Call creation complete: {statefulCall.Id}"); - return statefulCall; + throw new Exception("Call has already been added"); } - throw new Exception("Call has already been added"); + var statefulCall = await this.Client.Calls().AddAsync(joinParams, scenarioId).ConfigureAwait(false); + statefulCall.GraphLogger.Info($"Call creation complete: {statefulCall.Id}"); + _logger.LogInformation($"Call creation complete: {statefulCall.Id}"); + return statefulCall; } /// @@ -321,13 +324,13 @@ private void CallsOnUpdated(ICallCollection sender, CollectionEventArgs a foreach (var call in args.AddedResources) { var callHandler = new CallHandler(call, _settings, _logger); - var threadId = call.Resource.ChatInfo.ThreadId; + var threadId = call.Resource.ChatInfo?.ThreadId ?? call.Id; this.CallHandlers[threadId] = callHandler; } foreach (var call in args.RemovedResources) { - var threadId = call.Resource.ChatInfo.ThreadId; + var threadId = call.Resource.ChatInfo?.ThreadId ?? call.Id; if (this.CallHandlers.TryRemove(threadId, out CallHandler? handler)) { Task.Run(async () => {