From 1ac1cc1d47fa059501fd943b8b1e362f9bcdd0f6 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Tue, 7 Apr 2026 08:15:56 +0200 Subject: [PATCH] fix: share single CopilotClient across workflow agents Previously, CopilotAgentBundle.CreateAsync spawned a separate CopilotClient (and thus a separate Copilot CLI process) for each agent in a workflow. When multiple CLI processes started concurrently, their auto-login mechanisms could race on token refresh, causing subsequent agents to fail with 'Session was not created with authentication info or custom provider'. Share one CopilotClient across all agents in the bundle. Each agent still gets its own CopilotSession on the shared client. The bundle owns the client and disposes it after all agents. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Aryx.AgentHost/Services/CopilotAgentBundle.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sidecar/src/Aryx.AgentHost/Services/CopilotAgentBundle.cs b/sidecar/src/Aryx.AgentHost/Services/CopilotAgentBundle.cs index 0c478f6..a1ed4b3 100644 --- a/sidecar/src/Aryx.AgentHost/Services/CopilotAgentBundle.cs +++ b/sidecar/src/Aryx.AgentHost/Services/CopilotAgentBundle.cs @@ -52,12 +52,14 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable disposables.Add(toolingBundle); } + // Share a single CopilotClient across all agents to avoid spawning + // multiple CLI processes that race on token refresh during auto-login. + CopilotClient sharedClient = new(clientOptions); + await sharedClient.StartAsync(cancellationToken).ConfigureAwait(false); + IReadOnlyList agentNodes = command.Workflow.GetAgentNodes(); foreach ((WorkflowNodeDto definition, int agentIndex) in agentNodes.Select((definition, index) => (definition, index))) { - CopilotClient client = new(clientOptions); - await client.StartAsync(cancellationToken).ConfigureAwait(false); - SessionConfig sessionConfig = CreateSessionConfig( command, definition, @@ -72,9 +74,9 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable ApplyPromptInvocation(sessionConfig, command.PromptInvocation); AryxCopilotAgent agent = new( - client, + sharedClient, sessionConfig, - ownsClient: true, + ownsClient: false, id: definition.GetAgentId(), name: definition.GetAgentName(), description: NormalizeOptionalString(definition.Config.Description)); @@ -83,6 +85,9 @@ internal sealed class CopilotAgentBundle : IAsyncDisposable disposables.Add(agent); } + // The bundle owns the shared client — disposed after all agents. + disposables.Add(sharedClient); + CopilotAgentBundle bundle = new(agents, hasConfiguredHooks: !configuredHooks.IsEmpty); bundle._disposables.AddRange(disposables); return bundle;