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>
This commit is contained in:
David Kaya
2026-04-07 08:15:56 +02:00
co-authored by Copilot
parent bf72315735
commit 1ac1cc1d47
@@ -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<WorkflowNodeDto> 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;