diff --git a/packages/server/src/server/agent/providers/claude/agent.test.ts b/packages/server/src/server/agent/providers/claude/agent.test.ts index c3e828f457..84994220d8 100644 --- a/packages/server/src/server/agent/providers/claude/agent.test.ts +++ b/packages/server/src/server/agent/providers/claude/agent.test.ts @@ -1530,6 +1530,38 @@ describe("ClaudeAgentSession features", () => { await session.close(); }); + test("a patch that lands while Fast is being applied is not overwritten by it", async () => { + const { queryFactory, queryMock } = createQueryMock(); + const client = new ClaudeAgentClient({ + logger, + queryFactory, + resolveBinary: async () => "/test/claude/bin", + }); + const session = await client.createSession({ + provider: "claude", + cwd: process.cwd(), + model: "claude-opus-4-8", + }); + await (session as unknown as { ensureQuery(): Promise }).ensureQuery(); + let finishFast!: () => void; + queryMock.applyFlagSettings.mockImplementationOnce( + () => + new Promise((resolve) => { + finishFast = resolve; + }), + ); + + const fast = session.applyOptions?.({ fast_mode: true }); + await session.applyOptions?.({ allowedTools: ["Read"] }); + finishFast(); + await fast; + + expect( + (session as unknown as { config: { providerOptions: unknown } }).config.providerOptions, + ).toEqual({ fast_mode: true, allowedTools: ["Read"] }); + await session.close(); + }); + test("a patch before the first prompt keeps the restrictions the session opened with", async () => { const { queryFactory } = createQueryMock(); const client = new ClaudeAgentClient({ diff --git a/packages/server/src/server/agent/providers/claude/agent.ts b/packages/server/src/server/agent/providers/claude/agent.ts index b122793b3c..8b2743438d 100644 --- a/packages/server/src/server/agent/providers/claude/agent.ts +++ b/packages/server/src/server/agent/providers/claude/agent.ts @@ -2826,8 +2826,10 @@ class ClaudeAgentSession implements AgentSession { await this.query?.applyFlagSettings({ fastMode: providerOptions.fast_mode === true }); this.cachedRuntimeInfo = null; } - // Held until the runtime accepts, so a rejected change is retried instead of skipped. - this.config.providerOptions = providerOptions; + // Held until the runtime accepts, so a rejected change is retried instead of skipped. Only + // this call's keys are applied, over the options as they are now: another patch may have + // landed while the runtime was answering. + this.config.providerOptions = applyProviderOptionsPatch(this.config.providerOptions, options); } getPendingPermissions(): AgentPermissionRequest[] {