diff --git a/packages/server/src/server/agent/providers/opencode/v2/agent.test.ts b/packages/server/src/server/agent/providers/opencode/v2/agent.test.ts index 9cf1b34cb3..8435dccd90 100644 --- a/packages/server/src/server/agent/providers/opencode/v2/agent.test.ts +++ b/packages/server/src/server/agent/providers/opencode/v2/agent.test.ts @@ -50,6 +50,44 @@ describe("OpenCode v2 session lifecycle", () => { await session.close(); }); + test("a rejected auto_accept leaves the running session asking for permission", async () => { + const harness = new V2Harness(); + const permissions = [ + { id: "approval", sessionID: "session", action: "shell", resources: ["pwd"] }, + ]; + harness.api.permission.list = async () => permissions; + const replies: string[] = []; + harness.api.permission.reply = async (input) => { + replies.push(input.requestID); + throw new Error("upstream refused"); + }; + const client = new OpenCodeV2AgentClient({ + logger: createTestLogger(), + runtime: harness.runtime, + }); + const session = await client.createSession({ provider: "opencode", cwd: "/tmp/project" }); + try { + await expect(session.applyOptions?.({ auto_accept: true })).rejects.toThrow( + "upstream refused", + ); + + const next = { + id: "next", + sessionID: "session", + action: "shell", + resources: ["ls"], + }; + permissions.push(next); + harness.push({ id: "asked", created: 2, type: "permission.asked", data: next }); + + const pendingIds = () => session.getPendingPermissions().map(({ id }) => id); + await expect.poll(pendingIds).toContain("next"); + expect(replies).not.toContain("next"); + } finally { + await session.close(); + } + }); + test("rejects a live permission change the running session cannot apply", async () => { const harness = new V2Harness(); const client = new OpenCodeV2AgentClient({ diff --git a/packages/server/src/server/agent/providers/opencode/v2/session.ts b/packages/server/src/server/agent/providers/opencode/v2/session.ts index b442bcf523..a86f0c3619 100644 --- a/packages/server/src/server/agent/providers/opencode/v2/session.ts +++ b/packages/server/src/server/agent/providers/opencode/v2/session.ts @@ -376,11 +376,18 @@ export class OpenCodeV2Session implements AgentSession { ); if (!("auto_accept" in options)) return; const value = next?.auto_accept === true; - this.config.providerOptions = { ...this.config.providerOptions, auto_accept: value }; - if (value && !this.config.toolPolicy) - for (const request of this.permissions.list()) - if (request.kind !== "question") - await this.respondToPermission(request.id, { behavior: "allow" }); + const previous = this.config.providerOptions; + this.config.providerOptions = { ...previous, auto_accept: value }; + try { + if (value && !this.config.toolPolicy) + for (const request of this.permissions.list()) + if (request.kind !== "question") + await this.respondToPermission(request.id, { behavior: "allow" }); + } catch (error) { + // A rejected apply stores nothing, so the running session keeps asking as it did. + this.config.providerOptions = previous; + throw error; + } } async listCommands() { await this.reconnectIfExited();