From ce9cade572a797024a89422c4a4b9d8cce91bc79 Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Sun, 6 Sep 2026 14:47:23 +0000 Subject: [PATCH] surface per-app provider pins that silently override the task schedule An app's per-task provider/model pin always wins over the global Task Schedule's pin at spawn time (#4783), but nothing showed that a pin was even set unless you expanded that row's Configure panel. That let a schedule-level provider change look like it took effect when a stale per-app pin was still routing every run elsewhere -- e.g. layered- intelligence's schedule pin pointed at the fleet GPU OpenCode provider while migration 184's per-app pin (carried over from the old global job) kept routing it to the local Ollama provider instead, with no visible sign of the mismatch anywhere in the UI. Add a "Provider override" badge to the collapsed row whenever the app's pin names a different provider than the schedule's own pin, and extract the divergence check into a `providerPinDivergesFromSchedule` helper alongside the existing `hasProviderPin`/`providerPinPatch` pair so other surfaces can reuse it. --- .../components/apps/tabs/AutomationTab.jsx | 19 ++++++++++++-- .../apps/tabs/AutomationTab.test.jsx | 21 ++++++++++++++++ client/src/components/cos/constants.js | 12 +++++++++ client/src/components/cos/constants.test.js | 25 ++++++++++++++++++- 4 files changed, 74 insertions(+), 3 deletions(-) diff --git a/client/src/components/apps/tabs/AutomationTab.jsx b/client/src/components/apps/tabs/AutomationTab.jsx index ac819a2066..6c015d3e25 100644 --- a/client/src/components/apps/tabs/AutomationTab.jsx +++ b/client/src/components/apps/tabs/AutomationTab.jsx @@ -1,13 +1,13 @@ import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router'; -import { RefreshCw, Play, PauseCircle, Settings, ChevronDown, ChevronRight, Sparkles } from 'lucide-react'; +import { RefreshCw, Play, PauseCircle, Settings, ChevronDown, ChevronRight, Sparkles, AlertTriangle } from 'lucide-react'; import toast from '../../ui/Toast'; import BrailleSpinner from '../../BrailleSpinner'; import CronInput from '../../CronInput'; import ToggleSwitch from '../../ToggleSwitch'; import AppProviderPin from '../../cos/AppProviderPin'; import * as api from '../../../services/api'; -import { AGENT_OPTIONS, hasProviderPin, toggleAppMetadataOverride, agentOptionButtonClass } from '../../cos/constants'; +import { AGENT_OPTIONS, hasProviderPin, providerPinDivergesFromSchedule, toggleAppMetadataOverride, agentOptionButtonClass } from '../../cos/constants'; import { isCronExpression, describeCron } from '../../../utils/cronHelpers'; import { PROVIDER_TYPES, providerDisplayName } from '../../../utils/providers'; import CustomTasksSection from './CustomTasksSection'; @@ -251,6 +251,12 @@ export default function AutomationTab({ appId, appName }) { const effectiveProviderName = override.providerId ? providerDisplayName(providers, override.providerId) : taskProviderName; + // Surfaced collapsed (not just inside Configure): an app-level pin + // silently wins over the Schedule pin at spawn (#4783), so a task + // schedule showing one provider while this app's override names a + // DIFFERENT one is exactly the "why did it run somewhere else" + // confusion a user hits with only the Schedule page open. + const providerDivergesFromSchedule = providerPinDivergesFromSchedule(override, globalConfig); return (
@@ -276,6 +282,15 @@ export default function AutomationTab({ appId, appName }) {
{taskType} + {providerDivergesFromSchedule && ( + + + Provider override + + )}
{effectiveLabel}{intervalSuffix}