From d1ea8d8f74f6e20001656c6fa8c8c4acc0c93c42 Mon Sep 17 00:00:00 2001 From: James Ritchie Date: Mon, 9 Mar 2026 10:09:21 +0000 Subject: [PATCH 1/3] Fix(webapp) onboarding fixes (#3189) ### Fixes and improvements to the onboarding questions: **This change is worth double checking @matt-aitken** - Update to the Button.tsx file: it now takes `isLoading` that shows a spinner in the middle of the button (replacing the button text and any icons) and sets it to `disabled`. It does this nicely by keeping the button width the same so there's no layout shift. **Other fixes** - Fixes an issue where if you type a custom option in the "What technologies do you use" question, it doesn't check the list to see if it matches. Now it checks the box if you've typed an option from that list. - When we randomize the list of onboarding question options, we now store the position they appeared in the list --- .../onboarding/TechnologyPicker.tsx | 17 ++- .../app/components/primitives/Buttons.tsx | 135 +++++++++++------- .../route.tsx | 50 +++++-- .../webapp/app/routes/_app.orgs.new/route.tsx | 2 +- .../app/routes/confirm-basic-details.tsx | 33 ++++- 5 files changed, 169 insertions(+), 68 deletions(-) diff --git a/apps/webapp/app/components/onboarding/TechnologyPicker.tsx b/apps/webapp/app/components/onboarding/TechnologyPicker.tsx index 3d822e4b6..9c9f9a6b2 100644 --- a/apps/webapp/app/components/onboarding/TechnologyPicker.tsx +++ b/apps/webapp/app/components/onboarding/TechnologyPicker.tsx @@ -210,11 +210,22 @@ export function TechnologyPicker({ const addCustomValue = useCallback(() => { const trimmed = otherInputValue.trim(); - if (trimmed && !customValues.includes(trimmed) && !value.includes(trimmed)) { + if (!trimmed) return; + + const matchedOption = TECHNOLOGY_OPTIONS.find( + (opt) => opt.toLowerCase() === trimmed.toLowerCase() + ); + + if (matchedOption) { + if (!value.includes(matchedOption)) { + onChange([...value, matchedOption]); + } + } else if (!customValues.includes(trimmed) && !value.includes(trimmed)) { onCustomValuesChange([...customValues, trimmed]); - setOtherInputValue(""); } - }, [otherInputValue, customValues, onCustomValuesChange, value]); + + setOtherInputValue(""); + }, [otherInputValue, customValues, onCustomValuesChange, value, onChange]); const handleOtherKeyDown = useCallback( (e: React.KeyboardEvent) => { diff --git a/apps/webapp/app/components/primitives/Buttons.tsx b/apps/webapp/app/components/primitives/Buttons.tsx index 5c52a5d95..8ba196b5d 100644 --- a/apps/webapp/app/components/primitives/Buttons.tsx +++ b/apps/webapp/app/components/primitives/Buttons.tsx @@ -1,10 +1,18 @@ import { Link, type LinkProps, NavLink, type NavLinkProps } from "@remix-run/react"; -import React, { forwardRef, type ReactNode, useImperativeHandle, useRef } from "react"; +import React, { + forwardRef, + type ReactNode, + useEffect, + useImperativeHandle, + useRef, + useState, +} from "react"; import { type ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys"; import { cn } from "~/utils/cn"; import { ShortcutKey } from "./ShortcutKey"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./Tooltip"; import { Icon, type RenderIcon } from "./Icon"; +import { Spinner } from "./Spinner"; const sizes = { small: { @@ -180,6 +188,7 @@ export type ButtonContentPropsType = { tooltip?: ReactNode; iconSpacing?: string; hideShortcutKey?: boolean; + isLoading?: boolean; }; export function ButtonContent(props: ButtonContentPropsType) { @@ -196,7 +205,19 @@ export function ButtonContent(props: ButtonContentPropsType) { tooltip, iconSpacing, hideShortcutKey, + isLoading, } = props; + + const [showSpinner, setShowSpinner] = useState(false); + useEffect(() => { + if (!isLoading) { + setShowSpinner(false); + return; + } + const timer = setTimeout(() => setShowSpinner(true), 200); + return () => clearTimeout(timer); + }, [isLoading]); + const variation = allVariants.variant[props.variant]; const btnClassName = cn(allVariants.$all, variation.button); @@ -217,56 +238,64 @@ export function ButtonContent(props: ButtonContentPropsType) { const buttonContent = (
-
+
+ {LeadingIcon && ( + + )} + + {text && + (typeof text === "string" ? ( + + {text} + + ) : ( + <>{text} + ))} + + {shortcut && + !tooltip && + props.shortcutPosition === "before-trailing-icon" && + renderShortcutKey()} + + {TrailingIcon && ( + + )} + + {shortcut && + !tooltip && + (!props.shortcutPosition || props.shortcutPosition === "after-trailing-icon") && + renderShortcutKey()} +
+ {showSpinner && ( + + + )} - > - {LeadingIcon && ( - - )} - - {text && - (typeof text === "string" ? ( - - {text} - - ) : ( - <>{text} - ))} - - {shortcut && - !tooltip && - props.shortcutPosition === "before-trailing-icon" && - renderShortcutKey()} - - {TrailingIcon && ( - - )} - - {shortcut && - !tooltip && - (!props.shortcutPosition || props.shortcutPosition === "after-trailing-icon") && - renderShortcutKey()}
); @@ -298,6 +327,8 @@ export const Button = forwardRef( const innerRef = useRef(null); useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement); + const isDisabled = disabled || props.isLoading; + useShortcutKeys({ shortcut: props.shortcut, action: (e) => { @@ -307,14 +338,14 @@ export const Button = forwardRef( e.stopPropagation(); } }, - disabled: disabled || !props.shortcut, + disabled: isDisabled || !props.shortcut, }); return ( } cancelButton={ diff --git a/apps/webapp/app/routes/_app.orgs.new/route.tsx b/apps/webapp/app/routes/_app.orgs.new/route.tsx index b1d8fd83a..fb072518c 100644 --- a/apps/webapp/app/routes/_app.orgs.new/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.new/route.tsx @@ -220,7 +220,7 @@ export default function NewOrganizationPage() { + } diff --git a/apps/webapp/app/routes/confirm-basic-details.tsx b/apps/webapp/app/routes/confirm-basic-details.tsx index fad485e6d..ed8681865 100644 --- a/apps/webapp/app/routes/confirm-basic-details.tsx +++ b/apps/webapp/app/routes/confirm-basic-details.tsx @@ -4,7 +4,7 @@ import { ArrowRightIcon, EnvelopeIcon, UserGroupIcon, UserIcon } from "@heroicon import { HandRaisedIcon } from "@heroicons/react/24/solid"; import { RadioGroup } from "@radix-ui/react-radio-group"; import { json, type ActionFunction } from "@remix-run/node"; -import { Form, useActionData } from "@remix-run/react"; +import { Form, useActionData, useNavigation } from "@remix-run/react"; import { motion } from "framer-motion"; import { forwardRef, useEffect, useState } from "react"; import { z } from "zod"; @@ -99,6 +99,8 @@ function createSchema( referralSourceOther: z.string().optional(), role: z.string().optional(), roleOther: z.string().optional(), + referralSourcePosition: z.coerce.number().optional(), + rolePosition: z.coerce.number().optional(), }) .refine((value) => value.email === value.confirmEmail, { message: "Emails must match", @@ -141,6 +143,9 @@ export const action: ActionFunction = async ({ request }) => { if (submission.value.referralSource) { onboardingData.referralSource = submission.value.referralSource; + if (submission.value.referralSourcePosition) { + onboardingData.referralSourcePosition = String(submission.value.referralSourcePosition); + } if (submission.value.referralSource === "Other" && submission.value.referralSourceOther) { onboardingData.referralSourceOther = submission.value.referralSourceOther; } @@ -148,6 +153,9 @@ export const action: ActionFunction = async ({ request }) => { if (submission.value.role) { onboardingData.role = submission.value.role; + if (submission.value.rolePosition) { + onboardingData.rolePosition = String(submission.value.rolePosition); + } if (submission.value.role === "Other" && submission.value.roleOther) { onboardingData.roleOther = submission.value.roleOther; } @@ -201,6 +209,8 @@ export default function Page() { const lastSubmission = useActionData(); const [enteredEmail, setEnteredEmail] = useState(user.email ?? ""); const { isManagedCloud } = useFeatures(); + const navigation = useNavigation(); + const isSubmitting = navigation.state === "submitting" || navigation.state === "loading"; const [selectedReferralSource, setSelectedReferralSource] = useState(); const [selectedRole, setSelectedRole] = useState(""); @@ -317,6 +327,15 @@ export default function Page() { name="referralSource" value={selectedReferralSource ?? ""} /> + + value={selectedRole} setValue={setSelectedRole} @@ -384,7 +408,12 @@ export default function Page() { + } From 436f20efc6acde3b9dcb88b0621ff688bfd2b6d7 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 9 Mar 2026 12:14:43 +0000 Subject: [PATCH 2/3] feat(cli): auto-cancel dev runs on CLI exit via detached watchdog (#3191) When the dev CLI exits (e.g. ctrl+c via pnpm), runs that were mid-execution previously stayed stuck in EXECUTING status for up to 5 minutes until the heartbeat timeout fired. Now they are cancelled within seconds. The dev CLI spawns a lightweight detached watchdog process at startup. The watchdog monitors the CLI process ID and, when it detects the CLI has exited, calls a new POST /engine/v1/dev/disconnect endpoint to cancel all in-flight runs immediately (skipping PENDING_CANCEL since the worker is known to be dead). Watchdog design: - Fully detached (detached: true, stdio: ignore, unref()) so it survives even when pnpm sends SIGKILL to the process tree - Active run IDs maintained via atomic file write (.trigger/active-runs.json) - Single-instance guarantee via PID file (.trigger/watchdog.pid) - Safety timeout: exits after 24 hours to prevent zombie processes - On clean shutdown, the watchdog is killed (no disconnect needed) Disconnect endpoint: - Rate-limited: 5 calls/min per environment - Capped at 500 runs per call - Small counts (<= 25): cancelled inline with pMap concurrency 10 - Large counts: delegated to the bulk action system - Uses finalizeRun: true to skip PENDING_CANCEL and go straight to FINISHED Run engine change: - cancelRun() now respects finalizeRun when the run is in EXECUTING status, skipping the PENDING_CANCEL waiting state and going directly to FINISHED --- .changeset/strange-moles-provide.md | 6 + .server-changes/dev-cli-disconnect-md | 6 + .../app/routes/engine.v1.dev.disconnect.ts | 180 ++++++++++++++++++ .../v3/services/bulk/BulkActionV2.server.ts | 5 +- .../app/v3/services/cancelTaskRun.server.ts | 3 + .../src/engine/systems/runAttemptSystem.ts | 52 ++--- packages/cli-v3/src/apiClient.ts | 20 ++ packages/cli-v3/src/dev/devSupervisor.ts | 138 +++++++++++++- packages/cli-v3/src/dev/devWatchdog.ts | 174 +++++++++++++++++ packages/core/src/v3/schemas/api.ts | 11 ++ 10 files changed, 564 insertions(+), 31 deletions(-) create mode 100644 .changeset/strange-moles-provide.md create mode 100644 .server-changes/dev-cli-disconnect-md create mode 100644 apps/webapp/app/routes/engine.v1.dev.disconnect.ts create mode 100644 packages/cli-v3/src/dev/devWatchdog.ts diff --git a/.changeset/strange-moles-provide.md b/.changeset/strange-moles-provide.md new file mode 100644 index 000000000..3ad85ea07 --- /dev/null +++ b/.changeset/strange-moles-provide.md @@ -0,0 +1,6 @@ +--- +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Auto-cancel in-flight dev runs when the CLI exits, using a detached watchdog process that survives pnpm SIGKILL diff --git a/.server-changes/dev-cli-disconnect-md b/.server-changes/dev-cli-disconnect-md new file mode 100644 index 000000000..a0790d707 --- /dev/null +++ b/.server-changes/dev-cli-disconnect-md @@ -0,0 +1,6 @@ +--- +area: webapp +type: feature +--- + +Added `/engine/v1/dev/disconnect` endpoint to auto-cancel runs when the CLI disconnects. Maximum of 500 runs can be cancelled. Uses the bulk action system when there are more than 25 runs to cancel. \ No newline at end of file diff --git a/apps/webapp/app/routes/engine.v1.dev.disconnect.ts b/apps/webapp/app/routes/engine.v1.dev.disconnect.ts new file mode 100644 index 000000000..0cf92a53b --- /dev/null +++ b/apps/webapp/app/routes/engine.v1.dev.disconnect.ts @@ -0,0 +1,180 @@ +import { json } from "@remix-run/server-runtime"; +import { Ratelimit } from "@upstash/ratelimit"; +import { tryCatch } from "@trigger.dev/core"; +import { DevDisconnectRequestBody } from "@trigger.dev/core/v3"; +import { BulkActionId, RunId } from "@trigger.dev/core/v3/isomorphic"; +import { BulkActionNotificationType, BulkActionType } from "@trigger.dev/database"; +import { prisma } from "~/db.server"; +import { logger } from "~/services/logger.server"; +import { RateLimiter } from "~/services/rateLimiter.server"; +import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; +import { CancelTaskRunService } from "~/v3/services/cancelTaskRun.server"; +import { commonWorker } from "~/v3/commonWorker.server"; +import pMap from "p-map"; + +const CANCEL_REASON = "Dev session ended (CLI exited)"; + +// Below this threshold, cancel runs inline with pMap. +// Above it, create a bulk action and process asynchronously. +const BULK_ACTION_THRESHOLD = 25; + +// Maximum number of runs that can be cancelled in a single disconnect call. +const MAX_RUNS = 500; + +// Rate limit: 5 calls per minute per environment +const disconnectRateLimiter = new RateLimiter({ + keyPrefix: "dev-disconnect", + limiter: Ratelimit.fixedWindow(5, "1 m"), + logFailure: true, +}); + +const { action } = createActionApiRoute( + { + body: DevDisconnectRequestBody, + maxContentLength: 1024 * 256, // 256KB + method: "POST", + }, + async ({ authentication, body }) => { + // Only allow dev environments — this endpoint uses finalizeRun which + // skips PENDING_CANCEL and immediately finalizes executing runs. + if (authentication.environment.type !== "DEVELOPMENT") { + return json({ error: "This endpoint is only available for dev environments" }, { status: 403 }); + } + + const environmentId = authentication.environment.id; + + // Rate limit per environment + const rateLimitResult = await disconnectRateLimiter.limit(environmentId); + if (!rateLimitResult.success) { + return json( + { error: "Rate limit exceeded", retryAfter: Math.ceil((rateLimitResult.reset - Date.now()) / 1000) }, + { status: 429 } + ); + } + + if (body.runFriendlyIds.length > MAX_RUNS) { + return json( + { error: `A maximum of ${MAX_RUNS} runs can be cancelled per request` }, + { status: 400 } + ); + } + + const { runFriendlyIds } = body; + + if (runFriendlyIds.length === 0) { + return json({ cancelled: 0 }, { status: 200 }); + } + + logger.info("Dev disconnect: cancelling runs", { + environmentId, + runCount: runFriendlyIds.length, + }); + + // For small numbers of runs, cancel inline + if (runFriendlyIds.length <= BULK_ACTION_THRESHOLD) { + const cancelled = await cancelRunsInline(runFriendlyIds, environmentId); + return json({ cancelled }, { status: 200 }); + } + + // For large numbers, create a bulk action to process asynchronously + const bulkActionId = await createBulkCancelAction( + runFriendlyIds, + authentication.environment.project.id, + environmentId + ); + + logger.info("Dev disconnect: created bulk action for large run set", { + environmentId, + bulkActionId, + runCount: runFriendlyIds.length, + }); + + return json({ cancelled: 0, bulkActionId }, { status: 200 }); + } +); + +async function cancelRunsInline( + runFriendlyIds: string[], + environmentId: string +): Promise { + const runIds = runFriendlyIds.map((fid) => RunId.toId(fid)); + + const runs = await prisma.taskRun.findMany({ + where: { + id: { in: runIds }, + runtimeEnvironmentId: environmentId, + }, + select: { + id: true, + engine: true, + friendlyId: true, + status: true, + createdAt: true, + completedAt: true, + taskEventStore: true, + }, + }); + + let cancelled = 0; + const cancelService = new CancelTaskRunService(prisma); + + await pMap( + runs, + async (run) => { + const [error, result] = await tryCatch( + cancelService.call(run, { reason: CANCEL_REASON, finalizeRun: true }) + ); + + if (error) { + logger.error("Dev disconnect: failed to cancel run", { + runId: run.id, + error, + }); + } else if (result && !result.alreadyFinished) { + cancelled++; + } + }, + { concurrency: 10 } + ); + + logger.info("Dev disconnect: completed inline cancellation", { + environmentId, + cancelled, + total: runFriendlyIds.length, + }); + + return cancelled; +} + +async function createBulkCancelAction( + runFriendlyIds: string[], + projectId: string, + environmentId: string +): Promise { + const { id, friendlyId } = BulkActionId.generate(); + + await prisma.bulkActionGroup.create({ + data: { + id, + friendlyId, + projectId, + environmentId, + name: "Dev session disconnect", + type: BulkActionType.CANCEL, + params: { runId: runFriendlyIds, finalizeRun: true }, + queryName: "bulk_action_v1", + totalCount: runFriendlyIds.length, + completionNotification: BulkActionNotificationType.NONE, + }, + }); + + await commonWorker.enqueue({ + id: `processBulkAction-${id}`, + job: "processBulkAction", + payload: { bulkActionId: id }, + }); + + return friendlyId; +} + +export { action }; diff --git a/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts b/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts index 98b6079c1..4ca558bc2 100644 --- a/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts +++ b/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts @@ -138,11 +138,13 @@ export class BulkActionService extends BaseService { } // 2. Parse the params + const rawParams = group.params && typeof group.params === "object" ? group.params : {}; + const finalizeRun = "finalizeRun" in rawParams && (rawParams as any).finalizeRun === true; const filters = parseRunListInputOptions({ organizationId: group.project.organizationId, projectId: group.projectId, environmentId: group.environmentId, - ...(group.params && typeof group.params === "object" ? group.params : {}), + ...rawParams, }); const runsRepository = new RunsRepository({ @@ -199,6 +201,7 @@ export class BulkActionService extends BaseService { cancelService.call(run, { reason: `Bulk action ${group.friendlyId} cancelled run`, bulkActionId: bulkActionId, + finalizeRun, }) ); if (error) { diff --git a/apps/webapp/app/v3/services/cancelTaskRun.server.ts b/apps/webapp/app/v3/services/cancelTaskRun.server.ts index 0c27a0b95..76db56535 100644 --- a/apps/webapp/app/v3/services/cancelTaskRun.server.ts +++ b/apps/webapp/app/v3/services/cancelTaskRun.server.ts @@ -8,6 +8,8 @@ export type CancelTaskRunServiceOptions = { cancelAttempts?: boolean; cancelledAt?: Date; bulkActionId?: string; + /** Skip PENDING_CANCEL and finalize immediately (use when the worker is known to be dead). */ + finalizeRun?: boolean; }; type CancelTaskRunServiceResult = { @@ -57,6 +59,7 @@ export class CancelTaskRunService extends BaseService { runId: taskRun.id, completedAt: options?.cancelledAt, reason: options?.reason, + finalizeRun: options?.finalizeRun, bulkActionId: options?.bulkActionId, tx: this._prisma, }); diff --git a/internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts b/internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts index 2d10e756b..067d00a14 100644 --- a/internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts +++ b/internal-packages/run-engine/src/engine/systems/runAttemptSystem.ts @@ -1436,35 +1436,39 @@ export class RunAttemptSystem { }); //if executing, we need to message the worker to cancel the run and put it into `PENDING_CANCEL` status + //unless finalizeRun is true (worker is known to be dead), in which case skip straight to FINISHED if ( isExecuting(latestSnapshot.executionStatus) || isPendingExecuting(latestSnapshot.executionStatus) ) { - const newSnapshot = await this.executionSnapshotSystem.createExecutionSnapshot(prisma, { - run, - snapshot: { - executionStatus: "PENDING_CANCEL", - description: "Run was cancelled", - }, - previousSnapshotId: latestSnapshot.id, - environmentId: latestSnapshot.environmentId, - environmentType: latestSnapshot.environmentType, - projectId: latestSnapshot.projectId, - organizationId: latestSnapshot.organizationId, - workerId, - runnerId, - }); + if (!finalizeRun) { + const newSnapshot = await this.executionSnapshotSystem.createExecutionSnapshot(prisma, { + run, + snapshot: { + executionStatus: "PENDING_CANCEL", + description: "Run was cancelled", + }, + previousSnapshotId: latestSnapshot.id, + environmentId: latestSnapshot.environmentId, + environmentType: latestSnapshot.environmentType, + projectId: latestSnapshot.projectId, + organizationId: latestSnapshot.organizationId, + workerId, + runnerId, + }); - //the worker needs to be notified so it can kill the run and complete the attempt - await sendNotificationToWorker({ - runId, - snapshot: newSnapshot, - eventBus: this.$.eventBus, - }); - return { - alreadyFinished: false, - ...executionResultFromSnapshot(newSnapshot), - }; + //the worker needs to be notified so it can kill the run and complete the attempt + await sendNotificationToWorker({ + runId, + snapshot: newSnapshot, + eventBus: this.$.eventBus, + }); + return { + alreadyFinished: false, + ...executionResultFromSnapshot(newSnapshot), + }; + } + // finalizeRun is true — fall through to finish the run immediately } //not executing, so we will actually finish the run diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index e09926020..693b48d99 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -7,6 +7,8 @@ import { DevConfigResponseBody, DevDequeueRequestBody, DevDequeueResponseBody, + DevDisconnectRequestBody, + DevDisconnectResponseBody, EnvironmentVariableResponseBody, FailDeploymentRequestBody, FailDeploymentResponseBody, @@ -557,6 +559,7 @@ export class CliApiClient { heartbeatRun: this.devHeartbeatRun.bind(this), startRunAttempt: this.devStartRunAttempt.bind(this), completeRunAttempt: this.devCompleteRunAttempt.bind(this), + disconnect: this.devDisconnect.bind(this), setEngineURL: this.setEngineURL.bind(this), } as const; } @@ -681,6 +684,23 @@ export class CliApiClient { return eventSource; } + private async devDisconnect( + body: DevDisconnectRequestBody + ): Promise> { + if (!this.accessToken) { + throw new Error("devDisconnect: No access token"); + } + + return wrapZodFetch(DevDisconnectResponseBody, `${this.engineURL}/engine/v1/dev/disconnect`, { + method: "POST", + headers: { + Authorization: `Bearer ${this.accessToken}`, + Accept: "application/json", + }, + body: JSON.stringify(body), + }); + } + private async devDequeue( body: DevDequeueRequestBody ): Promise> { diff --git a/packages/cli-v3/src/dev/devSupervisor.ts b/packages/cli-v3/src/dev/devSupervisor.ts index 67da7e594..70a884283 100644 --- a/packages/cli-v3/src/dev/devSupervisor.ts +++ b/packages/cli-v3/src/dev/devSupervisor.ts @@ -1,3 +1,7 @@ +import { spawn, type ChildProcess } from "node:child_process"; +import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; import { setTimeout as awaitTimeout } from "node:timers/promises"; import { BuildManifest, @@ -71,6 +75,11 @@ class DevSupervisor implements WorkerRuntime { private runLimiter?: ReturnType; private taskRunProcessPool?: TaskRunProcessPool; + /** Detached watchdog process that cancels runs if the CLI is killed */ + private watchdogProcess?: ChildProcess; + private activeRunsPath?: string; + private watchdogPidPath?: string; + constructor(public readonly options: WorkerRuntimeOptions) {} async init(): Promise { @@ -138,25 +147,39 @@ class DevSupervisor implements WorkerRuntime { //start an SSE connection for presence this.disconnectPresence = await this.#startPresenceConnection(); - // Handle SIGTERM to gracefully stop all run controllers + // Handle SIGTERM/SIGINT to gracefully stop all run controllers process.on("SIGTERM", this.#handleSigterm); + process.on("SIGINT", this.#handleSigterm); + + // Spawn detached watchdog to cancel runs if CLI is killed (e.g. pnpm SIGKILL) + this.#spawnWatchdog(); //start dequeuing await this.#dequeueRuns(); } #handleSigterm = async () => { - logger.debug("[DevSupervisor] Received SIGTERM, stopping all run controllers"); + logger.debug("[DevSupervisor] Received SIGTERM/SIGINT, stopping all run controllers"); - const stopPromises = Array.from(this.runControllers.values()).map((controller) => - controller.stop() - ); + await this.shutdown(); - await Promise.allSettled(stopPromises); + // Must exit explicitly since registering a custom SIGINT handler + // overrides Node's default process termination behavior. + process.exit(0); }; async shutdown(): Promise { process.off("SIGTERM", this.#handleSigterm); + process.off("SIGINT", this.#handleSigterm); + + // Stop all local run controllers first so active-runs.json is up-to-date + const stopPromises = Array.from(this.runControllers.values()).map((controller) => + controller.stop() + ); + await Promise.allSettled(stopPromises); + + // Kill watchdog on clean shutdown — no disconnect needed since runs are stopped locally + this.#killWatchdog(); this.disconnectPresence?.(); try { @@ -177,6 +200,107 @@ class DevSupervisor implements WorkerRuntime { } } + #spawnWatchdog() { + const triggerDir = join(this.options.config.workingDir, ".trigger"); + if (!existsSync(triggerDir)) { + mkdirSync(triggerDir, { recursive: true }); + } + + this.activeRunsPath = join(triggerDir, "active-runs.json"); + this.watchdogPidPath = join(triggerDir, "watchdog.pid"); + + // Write empty active-runs file + this.#updateActiveRunsFile(); + + // Resolve the compiled watchdog script path relative to this file + const thisDir = fileURLToPath(new URL(".", import.meta.url)); + const watchdogScript = join(thisDir, "devWatchdog.js"); + + if (!existsSync(watchdogScript)) { + logger.debug("[DevSupervisor] Watchdog script not found, skipping", { watchdogScript }); + return; + } + + try { + this.watchdogProcess = spawn(process.execPath, [watchdogScript], { + detached: true, + stdio: "ignore", + env: { + ...process.env, + WATCHDOG_PARENT_PID: process.pid.toString(), + WATCHDOG_API_URL: this.config?.engineUrl ?? this.options.client.apiURL, + WATCHDOG_API_KEY: this.options.client.accessToken ?? "", + WATCHDOG_ACTIVE_RUNS: this.activeRunsPath, + WATCHDOG_PID_FILE: this.watchdogPidPath, + }, + }); + + this.watchdogProcess.unref(); + + logger.debug("[DevSupervisor] Spawned watchdog", { + watchdogPid: this.watchdogProcess.pid, + parentPid: process.pid, + }); + } catch (error) { + logger.debug("[DevSupervisor] Failed to spawn watchdog", { error }); + } + } + + #killWatchdog() { + const knownPid = this.watchdogProcess?.pid; + + if (knownPid) { + try { + process.kill(knownPid, "SIGTERM"); + } catch { + // Already dead + } + this.watchdogProcess = undefined; + } + + // Fallback: try via PID file, but only if the PID matches our spawned watchdog + // to avoid killing an unrelated process that reused a stale PID + if (this.watchdogPidPath) { + try { + const content = readFileSync(this.watchdogPidPath, "utf8"); + const prefix = "trigger-watchdog:"; + if (content.startsWith(prefix)) { + const pid = parseInt(content.slice(prefix.length), 10); + if (pid && (!knownPid || pid === knownPid)) { + process.kill(pid, "SIGTERM"); + } + } + } catch { + // Already dead or no file + } + } + + // Clean up files + try { + if (this.activeRunsPath) unlinkSync(this.activeRunsPath); + } catch {} + try { + if (this.watchdogPidPath) unlinkSync(this.watchdogPidPath); + } catch {} + } + + #updateActiveRunsFile() { + if (!this.activeRunsPath) return; + + try { + const data = { + parentPid: process.pid, + runFriendlyIds: Array.from(this.runControllers.keys()), + }; + // Atomic write: write to temp file then rename to avoid corrupt reads + const tmpPath = this.activeRunsPath + ".tmp"; + writeFileSync(tmpPath, JSON.stringify(data)); + renameSync(tmpPath, this.activeRunsPath); + } catch (error) { + logger.debug("[DevSupervisor] Failed to update active-runs file", { error }); + } + } + async initializeWorker( manifest: BuildManifest, metafile: Metafile, @@ -386,6 +510,7 @@ class DevSupervisor implements WorkerRuntime { //stop the run controller, and remove it runController?.stop(); this.runControllers.delete(message.run.friendlyId); + this.#updateActiveRunsFile(); this.#unsubscribeFromRunNotifications(message.run.friendlyId); //stop the worker if it is deprecated and there are no more runs @@ -402,6 +527,7 @@ class DevSupervisor implements WorkerRuntime { }); this.runControllers.set(message.run.friendlyId, runController); + this.#updateActiveRunsFile(); if (this.runLimiter) { this.runLimiter(() => runController.start(message)).then(() => { diff --git a/packages/cli-v3/src/dev/devWatchdog.ts b/packages/cli-v3/src/dev/devWatchdog.ts new file mode 100644 index 000000000..b3ccb7821 --- /dev/null +++ b/packages/cli-v3/src/dev/devWatchdog.ts @@ -0,0 +1,174 @@ +/** + * Dev Watchdog — a detached process that cancels in-flight runs when the dev CLI exits. + * + * Spawned by the dev CLI with `detached: true, stdio: "ignore", unref()`. + * Survives when pnpm sends SIGKILL to the CLI process tree. + * + * Lifecycle: + * 1. CLI spawns this script, passing config via env vars + * 2. Writes PID file for single-instance guarantee + * 3. Polls parent PID to detect when the CLI exits + * 4. On parent death: reads active-runs file → calls disconnect endpoint → exits + * + * Environment variables: + * WATCHDOG_PARENT_PID - The PID of the parent dev CLI process + * WATCHDOG_API_URL - The Trigger.dev API/engine URL + * WATCHDOG_API_KEY - The API key for authentication + * WATCHDOG_ACTIVE_RUNS - Path to the active-runs JSON file + * WATCHDOG_PID_FILE - Path to write the watchdog PID file + */ + +import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync } from "node:fs"; +import { dirname } from "node:path"; + +const POLL_INTERVAL_MS = 1000; + +// Safety timeout: if the watchdog has been running for 24 hours, exit regardless. +// Prevents zombie watchdogs from PID reuse scenarios. +const MAX_LIFETIME_MS = 24 * 60 * 60 * 1000; + +const parentPid = parseInt(process.env.WATCHDOG_PARENT_PID!, 10); +const apiUrl = process.env.WATCHDOG_API_URL!; +const apiKey = process.env.WATCHDOG_API_KEY!; +const activeRunsPath = process.env.WATCHDOG_ACTIVE_RUNS!; +const pidFilePath = process.env.WATCHDOG_PID_FILE!; + +if (!parentPid || !apiUrl || !apiKey || !activeRunsPath || !pidFilePath) { + process.exit(1); +} + +// Ensure directory exists +const dir = dirname(pidFilePath); +if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); +} + +const PID_FILE_PREFIX = "trigger-watchdog:"; + +// Single instance: kill any existing watchdog +try { + const pidFileContent = readFileSync(pidFilePath, "utf8"); + if (pidFileContent.startsWith(PID_FILE_PREFIX)) { + const existingPid = parseInt(pidFileContent.slice(PID_FILE_PREFIX.length), 10); + if (existingPid && existingPid !== process.pid) { + try { + process.kill(existingPid, 0); // Check if alive + process.kill(existingPid, "SIGTERM"); // Kill it + } catch { + // Already dead + } + } + } +} catch { + // No PID file or invalid format +} + +// Write our PID with prefix so we can verify ownership later +writeFileSync(pidFilePath, `${PID_FILE_PREFIX}${process.pid}`); + +function cleanup() { + try { + unlinkSync(pidFilePath); + } catch {} + try { + unlinkSync(activeRunsPath); + } catch {} +} + +function isParentAlive(): boolean { + try { + process.kill(parentPid, 0); + return true; + } catch { + return false; + } +} + +function readActiveRuns(): string[] { + try { + const data = JSON.parse(readFileSync(activeRunsPath, "utf8")); + return data.runFriendlyIds ?? []; + } catch { + return []; + } +} + +async function callDisconnect(runFriendlyIds: string[]): Promise { + const response = await fetch(`${apiUrl}/engine/v1/dev/disconnect`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ runFriendlyIds }), + signal: AbortSignal.timeout(10_000), + }); + + if (!response.ok) { + throw new Error(`Disconnect failed with status ${response.status}`); + } +} + +const MAX_DISCONNECT_ATTEMPTS = 5; +const INITIAL_BACKOFF_MS = 500; + +async function onParentDied(): Promise { + const runFriendlyIds = readActiveRuns(); + + if (runFriendlyIds.length > 0) { + for (let attempt = 0; attempt < MAX_DISCONNECT_ATTEMPTS; attempt++) { + try { + await callDisconnect(runFriendlyIds); + break; + } catch { + if (attempt < MAX_DISCONNECT_ATTEMPTS - 1) { + const backoff = INITIAL_BACKOFF_MS * 2 ** attempt; + await new Promise((resolve) => setTimeout(resolve, backoff)); + } + // Final attempt failed — runs will eventually time out via heartbeat + } + } + } + + cleanup(); + process.exit(0); +} + +// Guard against overlapping async callbacks +let checking = false; + +const interval = setInterval(async () => { + if (checking) return; + checking = true; + + try { + if (!isParentAlive()) { + clearInterval(interval); + clearTimeout(lifetimeTimeout); + await onParentDied(); + } + } finally { + checking = false; + } +}, POLL_INTERVAL_MS); + +// Safety timeout: exit after MAX_LIFETIME_MS to prevent zombie watchdogs +const lifetimeTimeout = setTimeout(() => { + clearInterval(interval); + cleanup(); + process.exit(0); +}, MAX_LIFETIME_MS); + +// Unref the timeout so it doesn't keep the process alive if the interval is cleared +lifetimeTimeout.unref(); + +// Clean exit on any termination signal +function handleSignal() { + clearInterval(interval); + clearTimeout(lifetimeTimeout); + cleanup(); + process.exit(0); +} + +process.on("SIGTERM", handleSignal); +process.on("SIGINT", handleSignal); diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index d42e61580..dc72f155b 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -838,6 +838,17 @@ export const DevDequeueResponseBody = z.object({ }); export type DevDequeueResponseBody = z.infer; +export const DevDisconnectRequestBody = z.object({ + runFriendlyIds: z.string().array(), +}); +export type DevDisconnectRequestBody = z.infer; + +export const DevDisconnectResponseBody = z.object({ + cancelled: z.number(), + bulkActionId: z.string().optional(), +}); +export type DevDisconnectResponseBody = z.infer; + export type CreateUploadPayloadUrlResponseBody = z.infer; export const ReplayRunResponse = z.object({ From c0b6309c72064833c78ace46ea8216610d46fac6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:17:24 +0000 Subject: [PATCH 3/3] chore: release v4.4.3 (#3182) ## Summary 2 new features, 2 improvements. ## Improvements - Add syncSupabaseEnvVars to pull database connection strings and save them as trigger.dev environment variables ([#3152](https://github.com/triggerdotdev/trigger.dev/pull/3152)) - Auto-cancel in-flight dev runs when the CLI exits, using a detached watchdog process that survives pnpm SIGKILL ([#3191](https://github.com/triggerdotdev/trigger.dev/pull/3191)) ## Server changes These changes affect the self-hosted Docker image and Trigger.dev Cloud: - A new Errors page for viewing and tracking errors that cause runs to fail - Errors are grouped using error fingerprinting - View top errors for a time period, filter by task, or search the text - View occurrences over time - View all the runs for an error and bulk replay them ([#3172](https://github.com/triggerdotdev/trigger.dev/pull/3172)) - Add sidebar tabs (Options, AI, Schema) to the Test page for schemaTask payload generation and schema viewing. ([#3188](https://github.com/triggerdotdev/trigger.dev/pull/3188))
Raw changeset output # Releases ## @trigger.dev/build@4.4.3 ### Patch Changes - Add syncSupabaseEnvVars to pull database connection strings and save them as trigger.dev environment variables ([#3152](https://github.com/triggerdotdev/trigger.dev/pull/3152)) - Updated dependencies: - `@trigger.dev/core@4.4.3` ## trigger.dev@4.4.3 ### Patch Changes - Auto-cancel in-flight dev runs when the CLI exits, using a detached watchdog process that survives pnpm SIGKILL ([#3191](https://github.com/triggerdotdev/trigger.dev/pull/3191)) - Updated dependencies: - `@trigger.dev/core@4.4.3` - `@trigger.dev/build@4.4.3` - `@trigger.dev/schema-to-json@4.4.3` ## @trigger.dev/core@4.4.3 ### Patch Changes - Auto-cancel in-flight dev runs when the CLI exits, using a detached watchdog process that survives pnpm SIGKILL ([#3191](https://github.com/triggerdotdev/trigger.dev/pull/3191)) ## @trigger.dev/python@4.4.3 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.4.3` - `@trigger.dev/build@4.4.3` - `@trigger.dev/sdk@4.4.3` ## @trigger.dev/react-hooks@4.4.3 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.4.3` ## @trigger.dev/redis-worker@4.4.3 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.4.3` ## @trigger.dev/rsc@4.4.3 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.4.3` ## @trigger.dev/schema-to-json@4.4.3 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.4.3` ## @trigger.dev/sdk@4.4.3 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.4.3`
--------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] --- .changeset/strange-moles-provide.md | 6 ------ .changeset/warm-falcons-joke.md | 5 ----- .server-changes/add-errors-page.md | 11 ----------- .server-changes/test-page-sidebar-tabs.md | 6 ------ packages/build/CHANGELOG.md | 8 ++++++++ packages/build/package.json | 4 ++-- packages/cli-v3/CHANGELOG.md | 10 ++++++++++ packages/cli-v3/package.json | 8 ++++---- packages/core/CHANGELOG.md | 6 ++++++ packages/core/package.json | 2 +- packages/python/CHANGELOG.md | 9 +++++++++ packages/python/package.json | 12 ++++++------ packages/react-hooks/CHANGELOG.md | 7 +++++++ packages/react-hooks/package.json | 4 ++-- packages/redis-worker/CHANGELOG.md | 7 +++++++ packages/redis-worker/package.json | 4 ++-- packages/rsc/CHANGELOG.md | 7 +++++++ packages/rsc/package.json | 6 +++--- packages/schema-to-json/CHANGELOG.md | 7 +++++++ packages/schema-to-json/package.json | 2 +- packages/trigger-sdk/CHANGELOG.md | 7 +++++++ packages/trigger-sdk/package.json | 4 ++-- pnpm-lock.yaml | 24 +++++++++++------------ 23 files changed, 103 insertions(+), 63 deletions(-) delete mode 100644 .changeset/strange-moles-provide.md delete mode 100644 .changeset/warm-falcons-joke.md delete mode 100644 .server-changes/add-errors-page.md delete mode 100644 .server-changes/test-page-sidebar-tabs.md diff --git a/.changeset/strange-moles-provide.md b/.changeset/strange-moles-provide.md deleted file mode 100644 index 3ad85ea07..000000000 --- a/.changeset/strange-moles-provide.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"trigger.dev": patch -"@trigger.dev/core": patch ---- - -Auto-cancel in-flight dev runs when the CLI exits, using a detached watchdog process that survives pnpm SIGKILL diff --git a/.changeset/warm-falcons-joke.md b/.changeset/warm-falcons-joke.md deleted file mode 100644 index 845473c1a..000000000 --- a/.changeset/warm-falcons-joke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/build": patch ---- - -Add syncSupabaseEnvVars to pull database connection strings and save them as trigger.dev environment variables diff --git a/.server-changes/add-errors-page.md b/.server-changes/add-errors-page.md deleted file mode 100644 index cdb2a1b6c..000000000 --- a/.server-changes/add-errors-page.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -area: webapp -type: feature ---- - -A new Errors page for viewing and tracking errors that cause runs to fail - -- Errors are grouped using error fingerprinting -- View top errors for a time period, filter by task, or search the text -- View occurrences over time -- View all the runs for an error and bulk replay them diff --git a/.server-changes/test-page-sidebar-tabs.md b/.server-changes/test-page-sidebar-tabs.md deleted file mode 100644 index 1803f2c2a..000000000 --- a/.server-changes/test-page-sidebar-tabs.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -area: webapp -type: feature ---- - -Add sidebar tabs (Options, AI, Schema) to the Test page for schemaTask payload generation and schema viewing. diff --git a/packages/build/CHANGELOG.md b/packages/build/CHANGELOG.md index 1da6d4c29..9e99c1f63 100644 --- a/packages/build/CHANGELOG.md +++ b/packages/build/CHANGELOG.md @@ -1,5 +1,13 @@ # @trigger.dev/build +## 4.4.3 + +### Patch Changes + +- Add syncSupabaseEnvVars to pull database connection strings and save them as trigger.dev environment variables ([#3152](https://github.com/triggerdotdev/trigger.dev/pull/3152)) +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/build/package.json b/packages/build/package.json index 76cb94cdd..cbde207ae 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/build", - "version": "4.4.2", + "version": "4.4.3", "description": "trigger.dev build extensions", "license": "MIT", "publishConfig": { @@ -78,7 +78,7 @@ }, "dependencies": { "@prisma/config": "^6.10.0", - "@trigger.dev/core": "workspace:4.4.2", + "@trigger.dev/core": "workspace:4.4.3", "mlly": "^1.7.1", "pkg-types": "^1.1.3", "resolve": "^1.22.8", diff --git a/packages/cli-v3/CHANGELOG.md b/packages/cli-v3/CHANGELOG.md index b0304d2e8..709c20918 100644 --- a/packages/cli-v3/CHANGELOG.md +++ b/packages/cli-v3/CHANGELOG.md @@ -1,5 +1,15 @@ # trigger.dev +## 4.4.3 + +### Patch Changes + +- Auto-cancel in-flight dev runs when the CLI exits, using a detached watchdog process that survives pnpm SIGKILL ([#3191](https://github.com/triggerdotdev/trigger.dev/pull/3191)) +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + - `@trigger.dev/build@4.4.3` + - `@trigger.dev/schema-to-json@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/cli-v3/package.json b/packages/cli-v3/package.json index 82257f67a..e5de14b9f 100644 --- a/packages/cli-v3/package.json +++ b/packages/cli-v3/package.json @@ -1,6 +1,6 @@ { "name": "trigger.dev", - "version": "4.4.2", + "version": "4.4.3", "description": "A Command-Line Interface for Trigger.dev projects", "type": "module", "license": "MIT", @@ -93,9 +93,9 @@ "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "1.36.0", "@s2-dev/streamstore": "^0.22.5", - "@trigger.dev/build": "workspace:4.4.2", - "@trigger.dev/core": "workspace:4.4.2", - "@trigger.dev/schema-to-json": "workspace:4.4.2", + "@trigger.dev/build": "workspace:4.4.3", + "@trigger.dev/core": "workspace:4.4.3", + "@trigger.dev/schema-to-json": "workspace:4.4.3", "ansi-escapes": "^7.0.0", "braces": "^3.0.3", "c12": "^1.11.1", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index f8b276c29..db5412c20 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,11 @@ # internal-platform +## 4.4.3 + +### Patch Changes + +- Auto-cancel in-flight dev runs when the CLI exits, using a detached watchdog process that survives pnpm SIGKILL ([#3191](https://github.com/triggerdotdev/trigger.dev/pull/3191)) + ## 4.4.2 ## 4.4.1 diff --git a/packages/core/package.json b/packages/core/package.json index 3bb7c7e00..1ecf044bf 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/core", - "version": "4.4.2", + "version": "4.4.3", "description": "Core code used across the Trigger.dev SDK and platform", "license": "MIT", "publishConfig": { diff --git a/packages/python/CHANGELOG.md b/packages/python/CHANGELOG.md index 75a6bae78..446e66012 100644 --- a/packages/python/CHANGELOG.md +++ b/packages/python/CHANGELOG.md @@ -1,5 +1,14 @@ # @trigger.dev/python +## 4.4.3 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + - `@trigger.dev/build@4.4.3` + - `@trigger.dev/sdk@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/python/package.json b/packages/python/package.json index cea36142e..c8c7dfcfe 100644 --- a/packages/python/package.json +++ b/packages/python/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/python", - "version": "4.4.2", + "version": "4.4.3", "description": "Python runtime and build extension for Trigger.dev", "license": "MIT", "publishConfig": { @@ -45,7 +45,7 @@ "check-exports": "attw --pack ." }, "dependencies": { - "@trigger.dev/core": "workspace:4.4.2", + "@trigger.dev/core": "workspace:4.4.3", "tinyexec": "^0.3.2" }, "devDependencies": { @@ -56,12 +56,12 @@ "tsx": "4.17.0", "esbuild": "^0.23.0", "@arethetypeswrong/cli": "^0.15.4", - "@trigger.dev/build": "workspace:4.4.2", - "@trigger.dev/sdk": "workspace:4.4.2" + "@trigger.dev/build": "workspace:4.4.3", + "@trigger.dev/sdk": "workspace:4.4.3" }, "peerDependencies": { - "@trigger.dev/sdk": "workspace:^4.4.2", - "@trigger.dev/build": "workspace:^4.4.2" + "@trigger.dev/sdk": "workspace:^4.4.3", + "@trigger.dev/build": "workspace:^4.4.3" }, "engines": { "node": ">=18.20.0" diff --git a/packages/react-hooks/CHANGELOG.md b/packages/react-hooks/CHANGELOG.md index a1d6b98bb..a0f9233c1 100644 --- a/packages/react-hooks/CHANGELOG.md +++ b/packages/react-hooks/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/react-hooks +## 4.4.3 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 1ab688b93..239c7d772 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/react-hooks", - "version": "4.4.2", + "version": "4.4.3", "description": "trigger.dev react hooks", "license": "MIT", "publishConfig": { @@ -37,7 +37,7 @@ "check-exports": "attw --pack ." }, "dependencies": { - "@trigger.dev/core": "workspace:^4.4.2", + "@trigger.dev/core": "workspace:^4.4.3", "swr": "^2.2.5" }, "devDependencies": { diff --git a/packages/redis-worker/CHANGELOG.md b/packages/redis-worker/CHANGELOG.md index 3be71e59d..eee31aa62 100644 --- a/packages/redis-worker/CHANGELOG.md +++ b/packages/redis-worker/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/redis-worker +## 4.4.3 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/redis-worker/package.json b/packages/redis-worker/package.json index 6e8ad6a47..fef723433 100644 --- a/packages/redis-worker/package.json +++ b/packages/redis-worker/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/redis-worker", - "version": "4.4.2", + "version": "4.4.3", "description": "Redis worker for trigger.dev", "license": "MIT", "publishConfig": { @@ -23,7 +23,7 @@ "test": "vitest --sequence.concurrent=false --no-file-parallelism" }, "dependencies": { - "@trigger.dev/core": "workspace:4.4.2", + "@trigger.dev/core": "workspace:4.4.3", "lodash.omit": "^4.5.0", "nanoid": "^5.0.7", "p-limit": "^6.2.0", diff --git a/packages/rsc/CHANGELOG.md b/packages/rsc/CHANGELOG.md index 247f0c08b..fe0155463 100644 --- a/packages/rsc/CHANGELOG.md +++ b/packages/rsc/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/rsc +## 4.4.3 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/rsc/package.json b/packages/rsc/package.json index a3dd0788a..b887f695b 100644 --- a/packages/rsc/package.json +++ b/packages/rsc/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/rsc", - "version": "4.4.2", + "version": "4.4.3", "description": "trigger.dev rsc", "license": "MIT", "publishConfig": { @@ -37,14 +37,14 @@ "check-exports": "attw --pack ." }, "dependencies": { - "@trigger.dev/core": "workspace:^4.4.2", + "@trigger.dev/core": "workspace:^4.4.3", "mlly": "^1.7.1", "react": "19.0.0-rc.1", "react-dom": "19.0.0-rc.1" }, "devDependencies": { "@arethetypeswrong/cli": "^0.15.4", - "@trigger.dev/build": "workspace:^4.4.2", + "@trigger.dev/build": "workspace:^4.4.3", "@types/node": "^20.14.14", "@types/react": "*", "@types/react-dom": "*", diff --git a/packages/schema-to-json/CHANGELOG.md b/packages/schema-to-json/CHANGELOG.md index 957145e2b..e24fa24b0 100644 --- a/packages/schema-to-json/CHANGELOG.md +++ b/packages/schema-to-json/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/schema-to-json +## 4.4.3 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/schema-to-json/package.json b/packages/schema-to-json/package.json index b283ebd2c..e2963c1d7 100644 --- a/packages/schema-to-json/package.json +++ b/packages/schema-to-json/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/schema-to-json", - "version": "4.4.2", + "version": "4.4.3", "description": "Convert various schema validation libraries to JSON Schema", "license": "MIT", "publishConfig": { diff --git a/packages/trigger-sdk/CHANGELOG.md b/packages/trigger-sdk/CHANGELOG.md index f7b1903fd..dbccf319f 100644 --- a/packages/trigger-sdk/CHANGELOG.md +++ b/packages/trigger-sdk/CHANGELOG.md @@ -1,5 +1,12 @@ # @trigger.dev/sdk +## 4.4.3 + +### Patch Changes + +- Updated dependencies: + - `@trigger.dev/core@4.4.3` + ## 4.4.2 ### Patch Changes diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index c6c7b04cc..e1ff05c4d 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@trigger.dev/sdk", - "version": "4.4.2", + "version": "4.4.3", "description": "trigger.dev Node.JS SDK", "license": "MIT", "publishConfig": { @@ -52,7 +52,7 @@ "dependencies": { "@opentelemetry/api": "1.9.0", "@opentelemetry/semantic-conventions": "1.36.0", - "@trigger.dev/core": "workspace:4.4.2", + "@trigger.dev/core": "workspace:4.4.3", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15afd915c..83e283083 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1382,7 +1382,7 @@ importers: specifier: ^6.10.0 version: 6.19.0(magicast@0.3.5) '@trigger.dev/core': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../core mlly: specifier: ^1.7.1 @@ -1458,13 +1458,13 @@ importers: specifier: ^0.22.5 version: 0.22.5(supports-color@10.0.0) '@trigger.dev/build': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../build '@trigger.dev/core': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../core '@trigger.dev/schema-to-json': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../schema-to-json ansi-escapes: specifier: ^7.0.0 @@ -1832,7 +1832,7 @@ importers: packages/python: dependencies: '@trigger.dev/core': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../core tinyexec: specifier: ^0.3.2 @@ -1842,10 +1842,10 @@ importers: specifier: ^0.15.4 version: 0.15.4 '@trigger.dev/build': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../build '@trigger.dev/sdk': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../trigger-sdk '@types/node': specifier: 20.14.14 @@ -1869,7 +1869,7 @@ importers: packages/react-hooks: dependencies: '@trigger.dev/core': - specifier: workspace:^4.4.2 + specifier: workspace:^4.4.3 version: link:../core react: specifier: ^18.0 || ^19.0 || ^19.0.0-rc @@ -1903,7 +1903,7 @@ importers: packages/redis-worker: dependencies: '@trigger.dev/core': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../core cron-parser: specifier: ^4.9.0 @@ -1952,7 +1952,7 @@ importers: packages/rsc: dependencies: '@trigger.dev/core': - specifier: workspace:^4.4.2 + specifier: workspace:^4.4.3 version: link:../core mlly: specifier: ^1.7.1 @@ -1968,7 +1968,7 @@ importers: specifier: ^0.15.4 version: 0.15.4 '@trigger.dev/build': - specifier: workspace:^4.4.2 + specifier: workspace:^4.4.3 version: link:../build '@types/node': specifier: 20.14.14 @@ -2044,7 +2044,7 @@ importers: specifier: 1.36.0 version: 1.36.0 '@trigger.dev/core': - specifier: workspace:4.4.2 + specifier: workspace:4.4.3 version: link:../core chalk: specifier: ^5.2.0