From 0d9babb986b635a6c94a9b6bb78f2d14fd8b2c6b Mon Sep 17 00:00:00 2001 From: Michael Chihlas Date: Wed, 15 Apr 2026 04:41:14 +0000 Subject: [PATCH] fix(rls): add account_id to AISessionStep creations, fix boards toast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - flowpilot_engine: pass account_id at all 5 AISessionStep instantiation sites (_create_step_from_parsed x3, briefing step, status update step). Phase 4 RLS blocked every INSERT with NULL account_id — this broke all new FlowPilot sessions since the Phase 4 migration was applied. - integrations: list_boards returns [] on PSAError instead of 502, stopping the spurious 'Server error' toast on dashboard load (boards are optional). - client.ts: 5xx global toast now shows backend detail when available. - useFlowPilotSession: startSession extracts backend detail for error state; suppresses duplicate toast for 5xx (global interceptor already handles it). Co-Authored-By: Claude Sonnet 4.6 --- backend/app/api/endpoints/integrations.py | 5 +++-- backend/app/services/flowpilot_engine.py | 7 +++++++ frontend/src/api/client.ts | 4 ++-- frontend/src/hooks/useFlowPilotSession.ts | 9 +++++++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/backend/app/api/endpoints/integrations.py b/backend/app/api/endpoints/integrations.py index 34152019..48fa9f57 100644 --- a/backend/app/api/endpoints/integrations.py +++ b/backend/app/api/endpoints/integrations.py @@ -362,8 +362,9 @@ async def list_boards( provider = await get_provider_for_account(current_user.account_id, db) boards = await provider.list_boards() return [PSABoardResponse(id=b.id, name=b.name) for b in boards] - except PSAError as e: - raise HTTPException(status_code=502, detail=str(e)) + except PSAError: + # Boards are optional UI chrome — degrade gracefully rather than surfacing a toast + return [] @router.get("/tickets/search", response_model=list[PSATicketSearchResult]) diff --git a/backend/app/services/flowpilot_engine.py b/backend/app/services/flowpilot_engine.py index 20c06299..1b280c29 100644 --- a/backend/app/services/flowpilot_engine.py +++ b/backend/app/services/flowpilot_engine.py @@ -330,6 +330,7 @@ async def start_session( # 7. Create first step step = _create_step_from_parsed( session_id=session.id, + account_id=session.account_id, step_order=0, parsed=parsed, input_tokens=input_tokens, @@ -433,6 +434,7 @@ async def process_response( # Create new step step = _create_step_from_parsed( session_id=session.id, + account_id=session.account_id, step_order=session.step_count - 1, parsed=parsed, input_tokens=input_tokens, @@ -694,6 +696,7 @@ async def pickup_session( briefing_step = AISessionStep( id=uuid.uuid4(), session_id=session.id, + account_id=session.account_id, branch_id=session.active_branch_id if session.is_branching else None, step_order=session.step_count, step_type="action", @@ -765,6 +768,7 @@ async def pickup_session( next_step = _create_step_from_parsed( session_id=session.id, + account_id=session.account_id, step_order=session.step_count - 1, parsed=parsed, input_tokens=input_tokens, @@ -997,6 +1001,7 @@ async def generate_status_update( step = AISessionStep( id=uuid.uuid4(), session_id=session.id, + account_id=session.account_id, branch_id=session.active_branch_id if session.is_branching else None, step_order=session.step_count, step_type="status_update", @@ -1440,6 +1445,7 @@ def _format_engineer_response(request: StepResponseRequest) -> str: def _create_step_from_parsed( session_id: UUID, + account_id: UUID, step_order: int, parsed: dict[str, Any], input_tokens: int, @@ -1487,6 +1493,7 @@ def _create_step_from_parsed( return AISessionStep( id=uuid.uuid4(), session_id=session_id, + account_id=account_id, branch_id=branch_id, step_order=step_order, step_type=step_type if parsed["type"] != "resolution_suggestion" else "action", diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 71e80ba5..89920130 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -49,9 +49,9 @@ function handleGlobalError(error: AxiosError) { return } - // Server errors (5xx) + // Server errors (5xx) — show backend detail when available, else generic message if (status >= 500) { - toast.error('Server error - please try again later') + toast.error(detail || 'Server error - please try again later') return } } diff --git a/frontend/src/hooks/useFlowPilotSession.ts b/frontend/src/hooks/useFlowPilotSession.ts index 873b3846..85b9d542 100644 --- a/frontend/src/hooks/useFlowPilotSession.ts +++ b/frontend/src/hooks/useFlowPilotSession.ts @@ -99,9 +99,14 @@ export function useFlowPilotSession(): UseFlowPilotSession { setAllSteps([firstStep]) setCurrentStep(firstStep) } catch (e: unknown) { - const message = e instanceof Error ? e.message : 'Failed to start session' + // Prefer the backend's detail message over the generic axios status string + const detail = (e as any)?.response?.data?.detail + const message = typeof detail === 'string' ? detail : (e instanceof Error ? e.message : 'Failed to start session') setError(message) - toast.error(message) + // Global axios interceptor already shows a toast for 5xx — skip duplicate + if (!(e as any)?.response?.status || (e as any)?.response?.status < 500) { + toast.error(message) + } } finally { setIsLoading(false) }