From 9ca6f24315db187ef046582cf408f24ef1125bad Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 13 Aug 2026 13:40:31 +0200 Subject: [PATCH] Let Claude apply its native MCP timeouts (#3315) * fix(claude): preserve native MCP timeouts Paseo forced Claude MCP startup and tool waits to ten minutes, turning unavailable servers into long silent first turns. Let Claude apply its own defaults while preserving explicit user environment overrides. * test(claude): isolate MCP timeout regression Exercise timeout precedence through the existing provider runtime environment input without mutating process-wide test state. * test(workspace-git): await refresh boundaries Synchronize fetch observation tests with initial snapshot and fetch completion before advancing fake timers. --- .../agent/providers/claude/agent.env.test.ts | 8 ++++ .../server/agent/providers/claude/agent.ts | 9 +--- .../workspace-git-service.observation.test.ts | 42 ++++++++++++------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/packages/server/src/server/agent/providers/claude/agent.env.test.ts b/packages/server/src/server/agent/providers/claude/agent.env.test.ts index 018894af6..642e562f6 100644 --- a/packages/server/src/server/agent/providers/claude/agent.env.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.env.test.ts @@ -68,6 +68,12 @@ describe("Claude SDK env", () => { logger: createTestLogger(), queryFactory, resolveBinary: async () => "/test/claude/bin", + runtimeSettings: { + env: { + MCP_TIMEOUT: "claude-startup-timeout", + MCP_TOOL_TIMEOUT: "claude-tool-timeout", + }, + }, }); const session = await client.createSession( { @@ -82,6 +88,8 @@ describe("Claude SDK env", () => { expect(result.sessionId).toBe("managed-agent-env-session"); expect(capturedEnv?.PASEO_AGENT_ID).toBe(launchContext.env?.PASEO_AGENT_ID); expect(capturedEnv?.PASEO_TEST_FLAG).toBe(launchContext.env?.PASEO_TEST_FLAG); + expect(capturedEnv?.MCP_TIMEOUT).toBe("claude-startup-timeout"); + expect(capturedEnv?.MCP_TOOL_TIMEOUT).toBe("claude-tool-timeout"); } finally { await session.close(); } diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index fcd6b4694..c02697c8d 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -3075,14 +3075,7 @@ class ClaudeAgentSession implements AgentSession { return createProviderEnv({ baseEnv: process.env, runtimeSettings: this.runtimeSettings, - overlays: [ - { - // Increase MCP timeouts for long-running tool calls (10 minutes) - MCP_TIMEOUT: "600000", - MCP_TOOL_TIMEOUT: "600000", - }, - this.launchEnv, - ], + overlays: [this.launchEnv], }); } diff --git a/packages/server/src/server/workspace-git-service.observation.test.ts b/packages/server/src/server/workspace-git-service.observation.test.ts index 61d90f212..165435a1f 100644 --- a/packages/server/src/server/workspace-git-service.observation.test.ts +++ b/packages/server/src/server/workspace-git-service.observation.test.ts @@ -503,6 +503,15 @@ describe("WorkspaceGitService checkout observation", () => { ...current, upstreamStatus: facts.upstreamStatus, })); + const runGitFetch = vi.fn(async (_cwd, observer) => { + observer?.onRefSnapshot("before"); + await releaseFetch.promise; + observer?.onRefSnapshot("after"); + return { + changes: [{ kind: "moved" as const, ref: "origin/main", beforeOid: "a", afterOid: "b" }], + error: null, + }; + }); const service = createService(watcher, { getCheckoutSnapshotFacts, getCheckoutRefDerivedState, @@ -515,22 +524,16 @@ describe("WorkspaceGitService checkout observation", () => { }), ), hasOriginRemote: vi.fn(async () => true), - runGitFetch: vi.fn(async (_cwd, observer) => { - observer?.onRefSnapshot("before"); - await releaseFetch.promise; - observer?.onRefSnapshot("after"); - return { - changes: [{ kind: "moved" as const, ref: "origin/main", beforeOid: "a", afterOid: "b" }], - error: null, - }; - }), + runGitFetch, }); const listener = vi.fn(); const subscription = service.registerWorkspace({ cwd: REPO_CWD }, listener); + await service.getSnapshot(REPO_CWD); await vi.waitFor(() => { + expect(runGitFetch).toHaveBeenCalledTimes(1); expect(service.getMetrics().fetchInFlightCount).toBe(1); - expect(service.getMetrics().workspaceRefreshInFlightCount).toBe(0); }); + expect(service.getMetrics().workspaceRefreshInFlightCount).toBe(0); listener.mockClear(); watcher.records @@ -539,7 +542,9 @@ describe("WorkspaceGitService checkout observation", () => { { path: path.join(GIT_DIR, "refs", "remotes", "origin", "main"), type: "create" }, ]); releaseFetch.resolve(); - await flushPromises(); + await vi.waitFor(() => { + expect(service.getMetrics().fetchInFlightCount).toBe(0); + }); await vi.advanceTimersByTimeAsync(1_000); await vi.waitFor(() => { expect(getCheckoutRefDerivedState).toHaveBeenCalledTimes(1); @@ -798,6 +803,10 @@ describe("WorkspaceGitService checkout observation", () => { ...current, upstreamStatus: facts.upstreamStatus, })); + const runGitFetch = vi.fn(async () => ({ + changes: [{ kind: "moved" as const, ref: "origin/main", beforeOid: "a", afterOid: "b" }], + error: null, + })); const service = createService(watcher, { getCheckoutSnapshotFacts, getCheckoutRefDerivedState, @@ -810,13 +819,16 @@ describe("WorkspaceGitService checkout observation", () => { }), ), hasOriginRemote: vi.fn(async () => true), - runGitFetch: vi.fn(async () => ({ - changes: [{ kind: "moved" as const, ref: "origin/main", beforeOid: "a", afterOid: "b" }], - error: null, - })), + runGitFetch, }); const subscription = service.registerWorkspace({ cwd: REPO_CWD }, vi.fn()); + await service.getSnapshot(REPO_CWD); + await vi.waitFor(() => { + expect(runGitFetch).toHaveBeenCalledTimes(1); + expect(service.getMetrics().fetchInFlightCount).toBe(0); + }); + expect(service.getMetrics().workspaceRefreshInFlightCount).toBe(0); await vi.advanceTimersByTimeAsync(1_000); await vi.waitFor(() => { expect(getCheckoutRefDerivedState).toHaveBeenCalledTimes(1);