fix: acp/zed race condition between MCP initialisation and prompt (#20205)

Signed-off-by: Kartik Angiras <angiraskartik@gmail.com>
This commit is contained in:
kartik
2026-02-28 23:03:08 +05:30
committed by GitHub
parent 6c65a2d813
commit b2214a6676
5 changed files with 39 additions and 2 deletions

View File

@@ -28,6 +28,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
const mockConfig = {
...params,
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: vi.fn(),
getExperiments: vi.fn().mockReturnValue({
flags: {
@@ -94,6 +95,7 @@ describe('loadConfig', () => {
const mockConfig = {
...(params as object),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
refreshAuth: vi.fn(),
getExperiments: vi.fn().mockReturnValue({
flags: {

View File

@@ -166,6 +166,8 @@ export async function loadConfig(
// Needed to initialize ToolRegistry, and git checkpointing if enabled
await config.initialize();
await config.waitForMcpInit();
startupProfiler.flush(config);
await refreshAuthentication(config, adcFilePath, 'Config');

View File

@@ -129,6 +129,7 @@ describe('GeminiAgent', () => {
mockConfig = {
refreshAuth: vi.fn(),
initialize: vi.fn(),
waitForMcpInit: vi.fn(),
getFileSystemService: vi.fn(),
setFileSystemService: vi.fn(),
getContentGeneratorConfig: vi.fn(),
@@ -486,6 +487,7 @@ describe('Session', () => {
getMessageBus: vi.fn().mockReturnValue(mockMessageBus),
setApprovalMode: vi.fn(),
isPlanEnabled: vi.fn().mockReturnValue(false),
waitForMcpInit: vi.fn(),
} as unknown as Mocked<Config>;
mockConnection = {
sessionUpdate: vi.fn(),
@@ -500,6 +502,28 @@ describe('Session', () => {
vi.clearAllMocks();
});
it('should await MCP initialization before processing a prompt', async () => {
const stream = createMockStream([
{
type: StreamEventType.CHUNK,
value: { candidates: [{ content: { parts: [{ text: 'Hi' }] } }] },
},
]);
mockChat.sendMessageStream.mockResolvedValue(stream);
await session.prompt({
sessionId: 'session-1',
prompt: [{ type: 'text', text: 'test' }],
});
expect(mockConfig.waitForMcpInit).toHaveBeenCalledOnce();
const waitOrder = (mockConfig.waitForMcpInit as Mock).mock
.invocationCallOrder[0];
const sendOrder = (mockChat.sendMessageStream as Mock).mock
.invocationCallOrder[0];
expect(waitOrder).toBeLessThan(sendOrder);
});
it('should handle prompt with text response', async () => {
const stream = createMockStream([
{

View File

@@ -521,6 +521,8 @@ export class Session {
const pendingSend = new AbortController();
this.pendingPrompt = pendingSend;
await this.config.waitForMcpInit();
const promptId = Math.random().toString(16).slice(2);
const chat = this.chat;

View File

@@ -716,6 +716,7 @@ export class Config implements McpContext {
private compressionTruncationCounter = 0;
private initialized = false;
private initPromise: Promise<void> | undefined;
private mcpInitializationPromise: Promise<void> | null = null;
readonly storage: Storage;
private readonly fileExclusions: FileExclusions;
private readonly eventEmitter?: EventEmitter;
@@ -1124,7 +1125,7 @@ export class Config implements McpContext {
);
// We do not await this promise so that the CLI can start up even if
// MCP servers are slow to connect.
const mcpInitialization = Promise.allSettled([
this.mcpInitializationPromise = Promise.allSettled([
this.mcpClientManager.startConfiguredMcpServers(),
this.getExtensionLoader().start(this),
]).then((results) => {
@@ -1136,7 +1137,7 @@ export class Config implements McpContext {
});
if (!this.interactive || this.experimentalZedIntegration) {
await mcpInitialization;
await this.mcpInitializationPromise;
}
if (this.skillsSupport) {
@@ -2234,6 +2235,12 @@ export class Config implements McpContext {
return this.experimentalZedIntegration;
}
async waitForMcpInit(): Promise<void> {
if (this.mcpInitializationPromise) {
await this.mcpInitializationPromise;
}
}
getListExtensions(): boolean {
return this.listExtensions;
}