From 6d94c565ee2e99ef8d91ca71e21b18eff3e3344a Mon Sep 17 00:00:00 2001 From: "cyrus@tinyhumans.ai" Date: Wed, 8 Jul 2026 01:06:23 +0530 Subject: [PATCH] fix(flows): default require_approval to false for builder-created workflows (frontend) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B3 (#4651) defaulted require_approval=false in the propose/revise tools + the server RPC, but two FRONTEND paths still forced it true, so builder-created flows persisted require_approval=1 and deadlocked on their first outbound tool (no thread/client to surface the approval card): - WorkflowPromptBar.tsx: the "describe a workflow" blank-flow create passed `true` to createFlow → now `false`. - ChatRuntimeProvider.tsx: parseWorkflowProposal mapped `require_approval !== false` (i.e. defaulted true when absent) → now `=== true` (defaults false, matching the server). A proposal only requires approval if the builder EXPLICITLY set it. The require_approval concept/field/column/gate/UI are all kept intact — this only flips the DEFAULT so flows don't ship approval-on-by-default. Users can still turn it on. typecheck clean; WorkflowPromptBar + ChatRuntimeProvider tests updated + green. --- .../components/flows/WorkflowPromptBar.test.tsx | 14 +++++++------- app/src/components/flows/WorkflowPromptBar.tsx | 9 +++++---- app/src/providers/ChatRuntimeProvider.tsx | 8 ++++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/components/flows/WorkflowPromptBar.test.tsx b/app/src/components/flows/WorkflowPromptBar.test.tsx index 50b5a29fb3..67b367eb60 100644 --- a/app/src/components/flows/WorkflowPromptBar.test.tsx +++ b/app/src/components/flows/WorkflowPromptBar.test.tsx @@ -35,15 +35,15 @@ describe('WorkflowPromptBar', () => { // The created flow is the standard blank graph (single manual trigger). expect(graph.nodes).toHaveLength(1); expect(graph.nodes[0].kind).toBe('trigger'); - // Parity with the proposal-card path: prompt-authored flows must default - // to requiring approval, matching WorkflowProposalCard's default. - expect(requireApproval).toBe(true); + // Prompt-authored flows default to NOT requiring approval, so a + // Run-button/scheduled run doesn't deadlock on an unsurfaceable approval. + expect(requireApproval).toBe(false); expect(navigateMock).toHaveBeenCalledWith('/flows/flow-1', { state: { copilotBuild: { description: 'digest my Slack every morning' } }, }); }); - it('defaults requireApproval to true so outbound side-effects need explicit approval', async () => { + it('defaults requireApproval to false so runs do not deadlock on an unsurfaceable approval', async () => { render(); fireEvent.change(screen.getByTestId('workflow-prompt-input'), { target: { value: 'auto-reply to every gmail thread' }, @@ -51,9 +51,9 @@ describe('WorkflowPromptBar', () => { fireEvent.click(screen.getByTestId('workflow-prompt-submit')); await waitFor(() => expect(createFlowMock).toHaveBeenCalledTimes(1)); - // Third positional arg must be `true` — flows_create's server default is - // `false`, so omitting it would silently disarm the approval gate. - expect(createFlowMock.mock.calls[0][2]).toBe(true); + // Third positional arg must be `false` — prompt-authored flows default to + // not requiring approval so a Run-button/scheduled run can execute. + expect(createFlowMock.mock.calls[0][2]).toBe(false); }); it('submits on Enter (Shift+Enter reserved for newlines)', async () => { diff --git a/app/src/components/flows/WorkflowPromptBar.tsx b/app/src/components/flows/WorkflowPromptBar.tsx index 69d5ae849e..b9ae14b728 100644 --- a/app/src/components/flows/WorkflowPromptBar.tsx +++ b/app/src/components/flows/WorkflowPromptBar.tsx @@ -44,13 +44,14 @@ export default function WorkflowPromptBar({ variant = 'compact', autoFocus = fal const name = deriveWorkflowName(trimmed, t('flows.page.newWorkflow')); log('submit: creating flow name=%s', name); try { - // Safe default: prompt-authored flows require approval so outbound - // Slack/Gmail/HTTP/code nodes cannot fire unattended. Omitting this - // arg would fall back to the server default of `false`. + // Prompt-authored flows default to NOT requiring approval — a + // Run-button/scheduled run has no chat context, so a parked approval + // card can't surface and the run would deadlock. The user can still + // turn approval on per-flow afterwards. const flow = await createFlow( name, createBlankWorkflowGraph(name, t('flows.nodeKind.trigger')), - true + false ); log('submit: created id=%s — opening canvas with build seed', flow.id); navigate(`/flows/${flow.id}`, { state: { copilotBuild: { description: trimmed } } }); diff --git a/app/src/providers/ChatRuntimeProvider.tsx b/app/src/providers/ChatRuntimeProvider.tsx index e93b985077..7b92591717 100644 --- a/app/src/providers/ChatRuntimeProvider.tsx +++ b/app/src/providers/ChatRuntimeProvider.tsx @@ -324,10 +324,10 @@ function parseWorkflowProposal(output: string): WorkflowProposal | null { return { name: obj.name, graph: obj.graph, - // The Rust tool defaults `require_approval` to `true` when the caller - // omits it, so treat anything other than an explicit `false` as `true` - // here too — keeps the client's fallback in lockstep with the server's. - requireApproval: obj.require_approval !== false, + // require_approval defaults to `false` — a proposal only requires approval + // if the builder explicitly set it — so treat anything other than an + // explicit `true` as `false`, in lockstep with the server default. + requireApproval: obj.require_approval === true, summary: { trigger: typeof summary.trigger === 'string' ? summary.trigger : '', steps }, }; }