From 1feb87317d06709f47a2dc8a25ac86d0fd76abf7 Mon Sep 17 00:00:00 2001
From: Cyrus Gray <144336577+graycyrus@users.noreply.github.com>
Date: Thu, 16 Jul 2026 02:24:17 +0530
Subject: [PATCH] fix(flows): show "awaiting approval" state in runs lists when
a run is halted at an approval gate (#4932)
---
.../components/flows/FlowRunsDrawer.test.tsx | 39 ++++
app/src/components/flows/FlowRunsDrawer.tsx | 12 +-
.../components/flows/FlowRunsSidebar.test.tsx | 33 +++
app/src/components/flows/FlowRunsSidebar.tsx | 54 +++--
.../useRunsPendingApprovalSet.test.ts | 214 ++++++++++++++++++
app/src/hooks/useRunsPendingApprovalSet.ts | 118 ++++++++++
app/src/pages/WorkflowRunsPage.test.tsx | 42 ++++
app/src/pages/WorkflowRunsPage.tsx | 58 +++--
8 files changed, 519 insertions(+), 51 deletions(-)
create mode 100644 app/src/hooks/__tests__/useRunsPendingApprovalSet.test.ts
create mode 100644 app/src/hooks/useRunsPendingApprovalSet.ts
diff --git a/app/src/components/flows/FlowRunsDrawer.test.tsx b/app/src/components/flows/FlowRunsDrawer.test.tsx
index 43cf8096a..d762ff680 100644
--- a/app/src/components/flows/FlowRunsDrawer.test.tsx
+++ b/app/src/components/flows/FlowRunsDrawer.test.tsx
@@ -23,6 +23,9 @@ import { FlowRunsDrawer } from './FlowRunsDrawer';
const listFlowRuns = vi.hoisted(() => vi.fn());
vi.mock('../../services/api/flowsApi', () => ({ listFlowRuns }));
+const fetchPendingApprovals = vi.hoisted(() => vi.fn());
+vi.mock('../../services/api/approvalApi', () => ({ fetchPendingApprovals }));
+
const FlowRunInspectorDrawer = vi.hoisted(() => vi.fn());
vi.mock('./FlowRunInspectorDrawer', () => ({
FLOW_RUN_STATUS_ACCENT: {
@@ -87,6 +90,7 @@ function renderDrawer(flowId: string | null, onClose: () => void, flowName?: str
describe('FlowRunsDrawer', () => {
beforeEach(() => {
vi.clearAllMocks();
+ fetchPendingApprovals.mockResolvedValue([]);
});
it('renders null when flowId is null', () => {
@@ -123,6 +127,41 @@ describe('FlowRunsDrawer', () => {
expect(row).toHaveTextContent('Completed with warnings');
});
+ it('shows "Awaiting approval" for a running run halted at a matching flow approval gate', async () => {
+ listFlowRuns.mockResolvedValue([makeRun({ id: 'run-1', status: 'running' })]);
+ fetchPendingApprovals.mockResolvedValue([
+ {
+ request_id: 'req-1',
+ tool_name: 'SLACK_SEND_MESSAGE',
+ action_summary: 'Send Slack message',
+ args_redacted: {},
+ session_id: 'session-1',
+ created_at: '2026-01-01T00:00:00Z',
+ expires_at: null,
+ source_context: { kind: 'flow', flow_id: 'flow-1', run_id: 'run-1' },
+ },
+ ]);
+ renderDrawer('flow-1', vi.fn());
+
+ const row = await screen.findByTestId('flow-run-row-run-1');
+ await waitFor(() => expect(row).toHaveTextContent('Awaiting approval'));
+ expect(screen.getByTestId('flow-run-row-dot-run-1').className.includes('dot-pending')).toBe(
+ true
+ );
+ });
+
+ it('leaves a running run without a matching flow approval labeled "Running"', async () => {
+ listFlowRuns.mockResolvedValue([makeRun({ id: 'run-1', status: 'running' })]);
+ fetchPendingApprovals.mockResolvedValue([]);
+ renderDrawer('flow-1', vi.fn());
+
+ const row = await screen.findByTestId('flow-run-row-run-1');
+ await waitFor(() => expect(row).toHaveTextContent('Running'));
+ expect(screen.getByTestId('flow-run-row-dot-run-1').className.includes('dot-running')).toBe(
+ true
+ );
+ });
+
it('falls back to a generic title when no flowName is given', async () => {
listFlowRuns.mockResolvedValue([]);
renderDrawer('flow-1', vi.fn());
diff --git a/app/src/components/flows/FlowRunsDrawer.tsx b/app/src/components/flows/FlowRunsDrawer.tsx
index 8f4484816..368c73b77 100644
--- a/app/src/components/flows/FlowRunsDrawer.tsx
+++ b/app/src/components/flows/FlowRunsDrawer.tsx
@@ -29,6 +29,10 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { useEscapeKey } from '../../hooks/useEscapeKey';
import { useFlowRunsLiveRefresh } from '../../hooks/useFlowRunsLiveRefresh';
+import {
+ resolveDisplayStatus,
+ useRunsPendingApprovalSet,
+} from '../../hooks/useRunsPendingApprovalSet';
import { useT } from '../../lib/i18n/I18nContext';
import { type FlowRun, listFlowRuns } from '../../services/api/flowsApi';
import {
@@ -142,6 +146,7 @@ export function FlowRunsDrawer({ flowId, flowName, onClose, onFixWithAgent }: Pr
}, [flowId]);
useFlowRunsLiveRefresh(runs, refetch);
+ const pendingRunIds = useRunsPendingApprovalSet(runs);
useEscapeKey(
() => {
@@ -213,6 +218,7 @@ export function FlowRunsDrawer({ flowId, flowName, onClose, onFixWithAgent }: Pr
{runs.map(run => {
const startedAt = formatTimestamp(run.started_at);
+ const displayStatus = resolveDisplayStatus(run, pendingRunIds);
return (
-