Skip to content

Commit cb11851

Browse files
committed
fix(mothership): stop orphaned job runs at their limit and skip any number of deleted occurrences
1 parent 8ec6dd4 commit cb11851

6 files changed

Lines changed: 30 additions & 16 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.test.tsx‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,6 @@ describe('UsageUpgradeDisplay', () => {
544544
)
545545
})
546546

547-
expect(container.textContent).toContain('Run Spend Limit Reached')
548-
expect(container.textContent).toContain('This scheduled job run reached its $5.00 spend limit.')
549547
expect(container.querySelector('a')).toBeNull()
550548
act(() => root.unmount())
551549
})

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3454,7 +3454,11 @@ function UsageUpgradeDisplay({ data }: { data: UsageUpgradeTagData }) {
34543454
* A scheduled job's run reached its own spend cap. The cap belongs to the job, not to the
34553455
* account, so the card offers no billing action: the next run starts with a fresh cap.
34563456
*/
3457-
function JobRunBudgetDisplay({ message }: { message: string }) {
3457+
interface JobRunBudgetDisplayProps {
3458+
message: string
3459+
}
3460+
3461+
function JobRunBudgetDisplay({ message }: JobRunBudgetDisplayProps) {
34583462
return (
34593463
<InteractionCard
34603464
title={

‎apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,15 +192,17 @@ interface UserMessageRowProps {
192192
* with one line. A background-task notification (mothership 21-background-tasks.md §6.4) shows
193193
* its outcome line; a scheduled job's run shows the brief's first line (the job and its run).
194194
*/
195+
interface AutomatedTurnRowProps {
196+
origin: TurnOrigin
197+
content: string
198+
rowClassName: string
199+
}
200+
195201
const AutomatedTurnRow = memo(function AutomatedTurnRow({
196202
origin,
197203
content,
198204
rowClassName,
199-
}: {
200-
origin: TurnOrigin
201-
content: string
202-
rowClassName: string
203-
}) {
205+
}: AutomatedTurnRowProps) {
204206
const lines = content.split('\n').filter((line) => line.length > 0)
205207
const summary =
206208
origin === 'task'

‎apps/sim/lib/mothership/jobs/occurrence.test.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,16 @@ describe('nextJobOccurrence', () => {
3333
new Date('2026-10-22T10:15:00Z')
3434
)
3535
})
36+
37+
it('skips any number of deleted occurrences instead of ending the job', () => {
38+
const everyMinute = { ...quarterHourly, cronExpression: '* * * * *' }
39+
const occurrenceAt = new Date('2026-10-22T10:00:00Z')
40+
const firstSlot = new Date('2026-10-22T10:15:00Z').getTime()
41+
const excludedDates = Array.from({ length: 1_500 }, (_, minute) =>
42+
new Date(firstSlot + minute * 60_000).toISOString()
43+
)
44+
expect(
45+
nextJobOccurrence({ ...everyMinute, excludedDates }, occurrenceAt, occurrenceAt)
46+
).toEqual(new Date(firstSlot + 1_500 * 60_000))
47+
})
3648
})

‎apps/sim/lib/mothership/jobs/occurrence.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,9 @@ export function nextJobOccurrence(cadence: JobCadence, occurrenceAt: Date, now:
4545
)
4646
)
4747
let next = cron.nextRun(earliest)
48-
for (let skipped = 0; next && excluded.has(next.toISOString()) && skipped < 1_000; skipped++) {
49-
next = cron.nextRun(next)
50-
}
51-
if (!next || excluded.has(next.toISOString())) return null
48+
/** Each step consumes one excluded instant, so this ends. */
49+
while (next && excluded.has(next.toISOString())) next = cron.nextRun(next)
50+
if (!next) return null
5251
if (cadence.endsAt && next.getTime() > cadence.endsAt.getTime()) return null
5352
return next
5453
}

‎apps/sim/lib/mothership/jobs/reconcile.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { copilotRuns, db, jobExecutionLogs, workflowSchedule } from '@sim/db'
1+
import { copilotRuns, db, jobExecutionLogs } from '@sim/db'
22
import { createLogger } from '@sim/logger'
33
import { getErrorMessage } from '@sim/utils/errors'
44
import { and, asc, eq, inArray, isNotNull, isNull, lte, or, sql } from 'drizzle-orm'
@@ -73,12 +73,11 @@ function openJobRuns(status: OpenJobRunStatus, now: Date) {
7373
admittedBillingRequestId,
7474
scheduleId: jobExecutionLogs.scheduleId,
7575
runStatus: copilotRuns.status,
76-
userId: workflowSchedule.sourceUserId,
77-
workspaceId: workflowSchedule.sourceWorkspaceId,
76+
userId: copilotRuns.userId,
77+
workspaceId: copilotRuns.workspaceId,
7878
chatId: copilotRuns.chatId,
7979
})
8080
.from(jobExecutionLogs)
81-
.leftJoin(workflowSchedule, eq(workflowSchedule.id, jobExecutionLogs.scheduleId))
8281
.leftJoin(copilotRuns, eq(copilotRuns.streamId, jobExecutionLogs.executionId))
8382
.where(and(openJobRun([status]), needsAction))
8483
.orderBy(asc(jobExecutionLogs.startedAt))

0 commit comments

Comments
 (0)