From 7506b00488def4a5305a909185637dc51ababeab Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Thu, 12 Mar 2026 07:43:40 -0700 Subject: [PATCH] fix(core): handle policy ALLOW for exit_plan_mode (#21802) --- .../core/src/tools/exit-plan-mode.test.ts | 20 +++++++++++++++++++ packages/core/src/tools/exit-plan-mode.ts | 12 +++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/core/src/tools/exit-plan-mode.test.ts b/packages/core/src/tools/exit-plan-mode.test.ts index 22de81fc7f..4b6b537d00 100644 --- a/packages/core/src/tools/exit-plan-mode.test.ts +++ b/packages/core/src/tools/exit-plan-mode.test.ts @@ -339,6 +339,26 @@ Ask the user for specific feedback on how to improve the plan.`, }); }); + describe('execute when shouldConfirmExecute is never called', () => { + it('should approve with DEFAULT mode when approvalPayload is null (policy ALLOW skips confirmation)', async () => { + const planRelativePath = createPlanFile('test.md', '# Content'); + const invocation = tool.build({ plan_path: planRelativePath }); + + // Simulate the scheduler's policy ALLOW path: execute() is called + // directly without ever calling shouldConfirmExecute(), leaving + // approvalPayload null. + const result = await invocation.execute(new AbortController().signal); + const expectedPath = path.join(mockPlansDir, 'test.md'); + + expect(result.llmContent).toContain('Plan approved'); + expect(result.returnDisplay).toContain('Plan approved'); + expect(mockConfig.setApprovalMode).toHaveBeenCalledWith( + ApprovalMode.DEFAULT, + ); + expect(mockConfig.setApprovedPlanPath).toHaveBeenCalledWith(expectedPath); + }); + }); + describe('getApprovalModeDescription (internal)', () => { it('should handle all valid approval modes', async () => { const planRelativePath = createPlanFile('test.md', '# Content'); diff --git a/packages/core/src/tools/exit-plan-mode.ts b/packages/core/src/tools/exit-plan-mode.ts index 442b00e5cb..b1615b18b4 100644 --- a/packages/core/src/tools/exit-plan-mode.ts +++ b/packages/core/src/tools/exit-plan-mode.ts @@ -203,8 +203,16 @@ export class ExitPlanModeInvocation extends BaseToolInvocation< }; } - const payload = this.approvalPayload; - if (payload?.approved) { + // When a user policy grants `allow` for exit_plan_mode, the scheduler + // skips the confirmation phase entirely and shouldConfirmExecute is never + // called, leaving approvalPayload null. Treat that as an approval with + // the default mode — consistent with the ALLOW branch inside + // shouldConfirmExecute. + const payload = this.approvalPayload ?? { + approved: true, + approvalMode: ApprovalMode.DEFAULT, + }; + if (payload.approved) { const newMode = payload.approvalMode ?? ApprovalMode.DEFAULT; if (newMode === ApprovalMode.PLAN || newMode === ApprovalMode.YOLO) {