diff --git a/packages/server/src/server/agent/providers/claude-agent.integration.test.ts b/packages/server/src/server/agent/providers/claude-agent.integration.test.ts index 10f10f01c..e109d47e4 100644 --- a/packages/server/src/server/agent/providers/claude-agent.integration.test.ts +++ b/packages/server/src/server/agent/providers/claude-agent.integration.test.ts @@ -213,6 +213,25 @@ describe("ClaudeAgentSession integration", () => { } }, 60_000); + test.runIf(canRunClaudeIntegration)( + "keeps bypassPermissions available after a thinking-option restart", + async () => { + const handle = await createSession({ + cwdPrefix: "claude-agent-bypass-restart-", + modeId: "bypassPermissions", + }); + + try { + await handle.session.setMode("acceptEdits"); + await handle.session.setThinkingOption("high"); + await expect(handle.session.setMode("bypassPermissions")).resolves.toBeUndefined(); + } finally { + await cleanupSession(handle); + } + }, + 60_000, + ); + test.runIf(canRunClaudeIntegration)( "supportedModels returns the current abstract Claude SDK model shape", async () => { diff --git a/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts b/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts index 37fd2a201..74bd40656 100644 --- a/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts +++ b/packages/server/src/server/agent/providers/claude-agent.redesign.test.ts @@ -783,6 +783,58 @@ describe("ClaudeAgentSession redesign invariants", () => { } }); + test("preserves bypass capability across query restarts triggered by thinking changes", async () => { + const capturedOptions: Array<{ + permissionMode?: string; + allowDangerouslySkipPermissions?: boolean; + effort?: string; + }> = []; + + sdkQueryFactory.mockImplementation( + ({ + options, + }: { + options: { + permissionMode?: string; + allowDangerouslySkipPermissions?: boolean; + effort?: string; + }; + }) => { + capturedOptions.push({ + permissionMode: options.permissionMode, + allowDangerouslySkipPermissions: options.allowDangerouslySkipPermissions, + effort: options.effort, + }); + + return createBaseQueryMock( + vi.fn(async () => ({ done: true, value: undefined })), + ); + }, + ); + + const session = await createSession(); + + try { + await session.setMode("bypassPermissions"); + await session.setMode("acceptEdits"); + await session.setThinkingOption("high"); + await session.setMode("bypassPermissions"); + + expect(capturedOptions).toHaveLength(2); + expect(capturedOptions[0]).toMatchObject({ + permissionMode: "default", + allowDangerouslySkipPermissions: true, + }); + expect(capturedOptions[1]).toMatchObject({ + permissionMode: "acceptEdits", + allowDangerouslySkipPermissions: true, + effort: "high", + }); + } finally { + await session.close(); + } + }); + test("reuses one autonomous run for unbound stream_event bursts with no foreground run", async () => { const session = await createSession(); const internal = session as unknown as { diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 3cfb20299..3b05dc5db 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -1889,6 +1889,10 @@ class ClaudeAgentSession implements AgentSession { cwd: this.config.cwd, includePartialMessages: true, permissionMode: this.currentMode, + // Dynamic mode switching can recreate the underlying Claude query. Keep the + // bypass launch capability available so later setPermissionMode("bypassPermissions") + // calls do not fail after a model/thinking/rewind-driven restart. + allowDangerouslySkipPermissions: true, agents: this.defaults?.agents, canUseTool: this.handlePermissionRequest, ...(claudeBinary ? { pathToClaudeCodeExecutable: claudeBinary } : {}),