From db4fb9eeefae32ef0509a698f208d6263bc92ad4 Mon Sep 17 00:00:00 2001
From: Iss <74388823+isshaddad@users.noreply.github.com>
Date: Wed, 4 Feb 2026 19:50:59 -0500
Subject: [PATCH 001/225] docs: Add Hookdeck example (#3005)
Add documentation for integrating Hookdeck with Trigger.dev to receive
webhooks and forward them to Trigger.dev tasks.
---
---
docs/docs.json | 3 +-
docs/guides/examples/hookdeck-webhook.mdx | 71 +++++++++++++++++++
.../frameworks/webhooks-guides-overview.mdx | 4 ++
docs/guides/introduction.mdx | 1 +
4 files changed, 78 insertions(+), 1 deletion(-)
create mode 100644 docs/guides/examples/hookdeck-webhook.mdx
diff --git a/docs/docs.json b/docs/docs.json
index c1f5d273804..324052905ac 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -352,7 +352,8 @@
"guides/frameworks/webhooks-guides-overview",
"guides/frameworks/nextjs-webhooks",
"guides/frameworks/remix-webhooks",
- "guides/examples/stripe-webhook"
+ "guides/examples/stripe-webhook",
+ "guides/examples/hookdeck-webhook"
]
}
]
diff --git a/docs/guides/examples/hookdeck-webhook.mdx b/docs/guides/examples/hookdeck-webhook.mdx
new file mode 100644
index 00000000000..a28949fca94
--- /dev/null
+++ b/docs/guides/examples/hookdeck-webhook.mdx
@@ -0,0 +1,71 @@
+---
+title: "Trigger tasks from Hookdeck webhooks"
+sidebarTitle: "Hookdeck webhooks"
+description: "This example demonstrates how to use Hookdeck to receive webhooks and trigger Trigger.dev tasks."
+---
+
+## Overview
+
+This example shows how to use [Hookdeck](https://hookdeck.com) as your webhook infrastructure to trigger Trigger.dev tasks. Hookdeck receives webhooks from external services, and forwards them directly to the Trigger.dev API. This gives you the best of both worlds: Hookdeck's webhook management, logging, and replay capabilities, combined with Trigger.dev's reliable task execution.
+
+## Key features
+
+- Use Hookdeck as your webhook endpoint for external services
+- Hookdeck forwards webhooks directly to Trigger.dev tasks via the API
+- All webhooks are logged and replayable in Hookdeck
+
+## Setting up Hookdeck
+
+You'll configure everything in the [Hookdeck dashboard](https://dashboard.hookdeck.com). No code changes needed in your app.
+
+### 1. Create a destination
+
+In Hookdeck, create a new [destination](https://hookdeck.com/docs/destinations) with the following settings:
+
+- **URL**: `https://api.trigger.dev/api/v1/tasks//trigger` (replace `` with your task ID)
+- **Method**: POST
+- **Authentication**: Bearer token (use your `TRIGGER_SECRET_KEY` from Trigger.dev)
+
+### 2. Add a transformation
+
+Create a [transformation](https://hookdeck.com/docs/transformations) to wrap the webhook body in the `payload` field that Trigger.dev expects:
+
+```javascript
+addHandler("transform", (request, context) => {
+ request.body = { payload: { ...request.body } };
+ return request;
+});
+```
+
+### 3. Create a connection
+
+Create a [connection](https://hookdeck.com/docs/connections) that links your source (where webhooks come from) to the destination and transformation you created above.
+
+## Task code
+
+This task will be triggered when Hookdeck forwards a webhook to the Trigger.dev API.
+
+```ts trigger/webhook-handler.ts
+import { task } from "@trigger.dev/sdk";
+
+export const webhookHandler = task({
+ id: "webhook-handler",
+ run: async (payload: Record) => {
+ // The payload contains the original webhook data from the external service
+ console.log("Received webhook:", payload);
+
+ // Add your custom logic here
+ },
+});
+```
+
+## Testing your setup
+
+To test everything is working:
+
+1. Set up your destination, transformation, and connection in [Hookdeck](https://dashboard.hookdeck.com)
+2. Send a test webhook to your Hookdeck source URL (use the Hookdeck Console or cURL)
+3. Check the Hookdeck dashboard to verify the webhook was received and forwarded
+4. Check the [Trigger.dev dashboard](https://cloud.trigger.dev) to see the successful run of your task
+
+For more information on setting up Hookdeck, refer to the [Hookdeck Documentation](https://hookdeck.com/docs).
diff --git a/docs/guides/frameworks/webhooks-guides-overview.mdx b/docs/guides/frameworks/webhooks-guides-overview.mdx
index 5e9703b53ef..4c0a4404276 100644
--- a/docs/guides/frameworks/webhooks-guides-overview.mdx
+++ b/docs/guides/frameworks/webhooks-guides-overview.mdx
@@ -31,6 +31,10 @@ A webhook handler is code that executes in response to an event. They can be end
How to create a Stripe webhook handler and trigger a task when a 'checkout session completed'
event is received.
+
+ Use Hookdeck to receive webhooks and forward them to Trigger.dev tasks with logging and replay
+ capabilities.
+
Date: Wed, 4 Feb 2026 17:17:18 -0800
Subject: [PATCH 002/225] fix(webapp): ask ai button missing tooltip (#2964)
Fixes
- the tooltip not displaying on the AskAI button in the side menu
- incorrect AskAI button heights
- Small UI tweaks
---
---------
Co-authored-by: Mihai Popescu
---
apps/webapp/app/components/AskAI.tsx | 21 ++++++++++---------
apps/webapp/app/components/Shortcuts.tsx | 3 ++-
.../app/components/navigation/SideMenu.tsx | 5 ++++-
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/apps/webapp/app/components/AskAI.tsx b/apps/webapp/app/components/AskAI.tsx
index bc55469b84a..814d4649c8f 100644
--- a/apps/webapp/app/components/AskAI.tsx
+++ b/apps/webapp/app/components/AskAI.tsx
@@ -118,30 +118,31 @@ function AskAIProvider({ websiteId, isCollapsed = false }: AskAIProviderProps) {
-
-
+
+
-
-
+
+
Ask AI
-
+
diff --git a/apps/webapp/app/components/Shortcuts.tsx b/apps/webapp/app/components/Shortcuts.tsx
index e3e4d6fe957..a3fcd074988 100644
--- a/apps/webapp/app/components/Shortcuts.tsx
+++ b/apps/webapp/app/components/Shortcuts.tsx
@@ -76,7 +76,8 @@ function ShortcutContent() {
-
+
+
diff --git a/apps/webapp/app/components/navigation/SideMenu.tsx b/apps/webapp/app/components/navigation/SideMenu.tsx
index 1e7d48cd57c..95282f15f5c 100644
--- a/apps/webapp/app/components/navigation/SideMenu.tsx
+++ b/apps/webapp/app/components/navigation/SideMenu.tsx
@@ -993,7 +993,10 @@ function CollapseToggle({
return (
{/* Vertical line to mask the side menu border */}
-
+
From 283f88b20382ee993d32f9fea48490898526b078 Mon Sep 17 00:00:00 2001
From: Saadi Myftija
Date: Thu, 5 Feb 2026 15:24:23 +0100
Subject: [PATCH 003/225] feat(webapp): add triggered via field to deployment
details page (#2850)
Display the deployment trigger source (CLI, CI/CD, Dashboard, GitHub
Integration) with appropriate icons on the deployment details page. The
triggeredVia field was already in the database but not displayed.
Co-authored-by: Claude
---
.../v3/DeploymentPresenter.server.ts | 2 +
.../route.tsx | 110 +++++++++++++++++-
2 files changed, 111 insertions(+), 1 deletion(-)
diff --git a/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts b/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
index ea59c657228..bc494c118aa 100644
--- a/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
+++ b/apps/webapp/app/presenters/v3/DeploymentPresenter.server.ts
@@ -156,6 +156,7 @@ export class DeploymentPresenter {
},
},
buildServerMetadata: true,
+ triggeredVia: true,
},
});
@@ -225,6 +226,7 @@ export class DeploymentPresenter {
isBuilt: !!deployment.builtAt,
type: deployment.type,
git: gitMetadata,
+ triggeredVia: deployment.triggeredVia,
},
};
}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx
index aebc934ba38..9d32d89fd56 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx
@@ -3,7 +3,16 @@ import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEffect, useState, useRef, useCallback } from "react";
import { S2, S2Error } from "@s2-dev/streamstore";
-import { Clipboard, ClipboardCheck, ChevronDown, ChevronUp } from "lucide-react";
+import {
+ Clipboard,
+ ClipboardCheck,
+ ChevronDown,
+ ChevronUp,
+ TerminalSquareIcon,
+ LayoutDashboardIcon,
+ GitBranchIcon,
+ ServerIcon,
+} from "lucide-react";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { GitMetadata } from "~/components/GitMetadata";
import { RuntimeIcon } from "~/components/RuntimeIcon";
@@ -73,6 +82,90 @@ type LogEntry = {
level: "info" | "error" | "warn" | "debug";
};
+function getTriggeredViaDisplay(triggeredVia: string | null | undefined): {
+ icon: React.ReactNode;
+ label: string;
+} | null {
+ if (!triggeredVia) return null;
+
+ const iconClass = "size-4 text-text-dimmed";
+
+ switch (triggeredVia) {
+ case "cli:manual":
+ return {
+ icon: ,
+ label: "CLI (Manual)",
+ };
+ case "cli:github_actions":
+ return {
+ icon: ,
+ label: "CLI (GitHub Actions)",
+ };
+ case "cli:gitlab_ci":
+ return {
+ icon: ,
+ label: "CLI (GitLab CI)",
+ };
+ case "cli:circleci":
+ return {
+ icon: ,
+ label: "CLI (CircleCI)",
+ };
+ case "cli:jenkins":
+ return {
+ icon: ,
+ label: "CLI (Jenkins)",
+ };
+ case "cli:azure_pipelines":
+ return {
+ icon: ,
+ label: "CLI (Azure Pipelines)",
+ };
+ case "cli:bitbucket_pipelines":
+ return {
+ icon: ,
+ label: "CLI (Bitbucket Pipelines)",
+ };
+ case "cli:travis_ci":
+ return {
+ icon: ,
+ label: "CLI (Travis CI)",
+ };
+ case "cli:buildkite":
+ return {
+ icon: ,
+ label: "CLI (Buildkite)",
+ };
+ case "cli:ci_other":
+ return {
+ icon: ,
+ label: "CLI (CI)",
+ };
+ case "git_integration:github":
+ return {
+ icon: ,
+ label: "GitHub Integration",
+ };
+ case "dashboard":
+ return {
+ icon: ,
+ label: "Dashboard",
+ };
+ default:
+ // Handle any unknown values gracefully
+ if (triggeredVia.startsWith("cli:")) {
+ return {
+ icon: ,
+ label: `CLI (${triggeredVia.replace("cli:", "")})`,
+ };
+ }
+ return {
+ icon: ,
+ label: triggeredVia,
+ };
+ }
+}
+
export default function Page() {
const { deployment, eventStream } = useTypedLoaderData();
const organization = useOrganization();
@@ -408,6 +501,21 @@ export default function Page() {
)}
+
+ Triggered via
+
+ {(() => {
+ const display = getTriggeredViaDisplay(deployment.triggeredVia);
+ if (!display) return "–";
+ return (
+
+ {display.icon}
+ {display.label}
+
+ );
+ })()}
+
+
From 3bb9aac01405b259003f4a223f1bc87b4c63338e Mon Sep 17 00:00:00 2001
From: James Ritchie
Date: Thu, 5 Feb 2026 07:45:40 -0800
Subject: [PATCH 004/225] Fix(webapp): Prevent big numbers on Queue page from
jumping around when animating (#3007)
---
.../route.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
index 3a8a7544c5b..3ea70e1e18a 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
@@ -364,14 +364,14 @@ export default function Page() {
/>
}
- valueClassName={env.paused ? "text-warning" : undefined}
+ valueClassName={cn(env.paused ? "text-warning" : undefined, "tabular-nums")}
compactThreshold={1000000}
/>
From b96a0b70d49b04d3a29b803b720a3dac6be7e8c1 Mon Sep 17 00:00:00 2001
From: DKP <8297864+D-K-P@users.noreply.github.com>
Date: Thu, 5 Feb 2026 23:22:44 +0000
Subject: [PATCH 005/225] Docs: Clarify AI tool compatibility and expand
context snippet (#3000)
This pull request overhauls the "Building with AI" documentation
section. It includes a comprehensive restructuring of the main
building-with-ai page with new setup guides and troubleshooting
sections, reorganizes the navigation hierarchy to elevate
mcp-agent-rules as a top-level page, and updates multiple documentation
pages to clarify the relationships between three AI tools: Skills, Agent
Rules, and MCP Server. Changes also include formatting improvements,
such as replacing italicized text with inline code formatting, and
consistent additions of explanatory Note blocks and CardGroup components
across related pages.
---
docs/building-with-ai.mdx | 275 ++++++++++++++++++++++++++++++++++++--
docs/docs.json | 5 +-
docs/mcp-agent-rules.mdx | 23 +++-
docs/mcp-introduction.mdx | 12 +-
docs/mcp-tools.mdx | 60 ++++-----
docs/skills.mdx | 12 +-
6 files changed, 334 insertions(+), 53 deletions(-)
diff --git a/docs/building-with-ai.mdx b/docs/building-with-ai.mdx
index ba8cd5bb47c..e551324cadf 100644
--- a/docs/building-with-ai.mdx
+++ b/docs/building-with-ai.mdx
@@ -4,21 +4,272 @@ sidebarTitle: "Overview"
description: "Tools and resources for building Trigger.dev projects with AI coding assistants."
---
-We provide tools to help you build Trigger.dev projects with AI coding assistants. We recommend using them for the best developer experience.
+## Quick setup
-
-
- Give your AI assistant direct access to Trigger.dev tools - search docs, trigger tasks, deploy projects, and monitor runs.
+We provide multiple tools to help AI coding assistants write correct Trigger.dev code. Use one or all of them for the best developer experience.
+
+
+
+
+
+ Give your AI assistant direct access to Trigger.dev tools — search docs, trigger tasks, deploy projects, and monitor runs. Works with Claude Code, Cursor, Windsurf, VS Code (Copilot), and Zed.
+
+ ```bash
+ npx trigger.dev@latest install-mcp
+ ```
+
+ [Learn more →](/mcp-introduction)
+
+
+
+ Portable instruction sets that teach any AI coding assistant Trigger.dev best practices. Works with Claude Code, Cursor, Windsurf, VS Code (Copilot), and any tool that supports the [Agent Skills standard](https://agentskills.io).
+
+ ```bash
+ npx skills add triggerdotdev/skills
+ ```
+
+ [Learn more →](/skills)
+
+
+
+ Comprehensive rule sets installed directly into your AI client's config files. Works with Cursor, Claude Code, VS Code (Copilot), Windsurf, Gemini CLI, Cline, and more. Claude Code also gets a dedicated subagent for hands-on help.
+
+ ```bash
+ npx trigger.dev@latest install-rules
+ ```
+
+ [Learn more →](/mcp-agent-rules)
+
+
+
+
+## Skills vs Agent Rules vs MCP
+
+Not sure which tool to use? Here's how they compare:
+
+| | **Skills** | **Agent Rules** | **MCP Server** |
+|:--|:-----------|:----------------|:---------------|
+| **What it does** | Drops skill files into your project | Installs rule sets into client config | Runs a live server your AI connects to |
+| **Installs to** | `.claude/skills/`, `.cursor/skills/`, etc. | `.cursor/rules/`, `CLAUDE.md`, `AGENTS.md`, etc. | `mcp.json`, `~/.claude.json`, etc. |
+| **Updates** | Re-run `npx skills add` | Re-run `npx trigger.dev@latest install-rules` or auto-prompted on `trigger dev` | Always latest (uses `@latest`) |
+| **Best for** | Teaching patterns and best practices | Comprehensive code generation guidance | Live project interaction (deploy, trigger, monitor) |
+| **Works offline** | Yes | Yes | No (calls Trigger.dev API) |
+
+**Our recommendation:** Install all three. Skills and Agent Rules teach your AI *how* to write code. The MCP Server lets it *do things* in your project.
+
+## Project-level context snippet
+
+If you prefer a lightweight/passive approach, paste the snippet below into a context file at the root of your project. Different AI tools read different files:
+
+| File | Read by |
+|:-----|:--------|
+| `CLAUDE.md` | Claude Code |
+| `AGENTS.md` | OpenAI Codex, Jules, OpenCode |
+| `.cursor/rules/*.md` | Cursor |
+| `.github/copilot-instructions.md` | GitHub Copilot |
+| `CONVENTIONS.md` | Windsurf, Cline, and others |
+
+Create the file that matches your AI tool (or multiple files if your team uses different tools) and paste the snippet below. This gives the AI essential Trigger.dev context without installing anything.
+
+
+
+````markdown
+# Trigger.dev rules
+
+## Imports
+
+Always import from `@trigger.dev/sdk` — never from `@trigger.dev/sdk/v3` or use the deprecated `client.defineJob` pattern.
+
+## Task pattern
+
+Every task must be exported. Use `task()` from `@trigger.dev/sdk`:
+
+```ts
+import { task } from "@trigger.dev/sdk";
+
+export const myTask = task({
+ id: "my-task",
+ retry: {
+ maxAttempts: 3,
+ factor: 1.8,
+ minTimeoutInMs: 500,
+ maxTimeoutInMs: 30_000,
+ },
+ run: async (payload: { url: string }) => {
+ // No timeouts — runs can take as long as needed
+ return { success: true };
+ },
+});
+```
+
+## Triggering tasks
+
+From your backend (Next.js route, Express handler, etc.):
+
+```ts
+import type { myTask } from "./trigger/my-task";
+import { tasks } from "@trigger.dev/sdk";
+
+// Fire and forget
+const handle = await tasks.trigger("my-task", { url: "https://example.com" });
+
+// Batch trigger (up to 1,000 items)
+const batchHandle = await tasks.batchTrigger("my-task", [
+ { payload: { url: "https://example.com/1" } },
+ { payload: { url: "https://example.com/2" } },
+]);
+```
+
+### From inside other tasks
+
+```ts
+export const parentTask = task({
+ id: "parent-task",
+ run: async (payload) => {
+ // Fire and forget
+ await childTask.trigger({ data: "value" });
+
+ // Wait for result — returns a Result object, NOT the output directly
+ const result = await childTask.triggerAndWait({ data: "value" });
+ if (result.ok) {
+ console.log(result.output); // The actual return value
+ } else {
+ console.error(result.error);
+ }
- ```bash
- npx trigger.dev@latest install-mcp
- ```
+ // Or use .unwrap() to get output directly (throws on failure)
+ const output = await childTask.triggerAndWait({ data: "value" }).unwrap();
+ },
+});
+```
+
+> Never wrap `triggerAndWait` or `batchTriggerAndWait` in `Promise.all` — this is not supported.
+
+## Error handling
+
+```ts
+import { task, retry, AbortTaskRunError } from "@trigger.dev/sdk";
+
+export const resilientTask = task({
+ id: "resilient-task",
+ retry: { maxAttempts: 5 },
+ run: async (payload) => {
+ // Permanent error — skip retrying
+ if (!payload.isValid) {
+ throw new AbortTaskRunError("Invalid payload, will not retry");
+ }
+
+ // Retry a specific block (not the whole task)
+ const data = await retry.onThrow(
+ async () => await fetchExternalApi(payload),
+ { maxAttempts: 3 }
+ );
+
+ return data;
+ },
+});
+```
+
+## Schema validation
+
+Use `schemaTask` with Zod for payload validation:
+
+```ts
+import { schemaTask } from "@trigger.dev/sdk";
+import { z } from "zod";
+
+export const processVideo = schemaTask({
+ id: "process-video",
+ schema: z.object({ videoUrl: z.string().url() }),
+ run: async (payload) => {
+ // payload is typed and validated
+ },
+});
+```
+
+## Waits
+
+Use `wait.for` for delays, `wait.until` for dates, and `wait.forToken` for external callbacks:
+
+```ts
+import { wait } from "@trigger.dev/sdk";
+await wait.for({ seconds: 30 });
+await wait.until({ date: new Date("2025-01-01") });
+```
+
+## Configuration
+
+`trigger.config.ts` lives at the project root:
+
+```ts
+import { defineConfig } from "@trigger.dev/sdk/build";
+
+export default defineConfig({
+ project: "",
+ dirs: ["./trigger"],
+});
+```
+
+## Common mistakes
+
+1. **Forgetting to export tasks** — every task must be a named export
+2. **Importing from `@trigger.dev/sdk/v3`** — this is the old v3 path; always use `@trigger.dev/sdk`
+3. **Using `client.defineJob()`** — this is the deprecated v2 API
+4. **Calling `task.trigger()` directly** — use `tasks.trigger("task-id", payload)` from your backend
+5. **Using `triggerAndWait` result as output** — it returns a `Result` object; check `result.ok` then access `result.output`, or use `.unwrap()`
+6. **Wrapping waits/triggerAndWait in `Promise.all`** — not supported in Trigger.dev tasks
+7. **Adding timeouts to tasks** — tasks have no built-in timeout; use `maxDuration` in config if needed
+````
+
+
+
+## llms.txt
+
+We also publish machine-readable documentation for LLM consumption:
+
+- [trigger.dev/docs/llms.txt](https://trigger.dev/docs/llms.txt) — concise overview
+- [trigger.dev/docs/llms-full.txt](https://trigger.dev/docs/llms-full.txt) — full documentation
+
+These follow the [llms.txt standard](https://llmstxt.org) and can be fed directly into any LLM context window.
+
+
+## Troubleshooting
+
+
+
+
+ Install [Agent Rules](/mcp-agent-rules) or [Skills](/skills) — they override the outdated patterns in the AI's training data. The [context snippet](#project-level-context-snippet) above is a quick alternative.
+
+
+
+ 1. Make sure you've restarted your AI client after adding the config
+ 2. Run `npx trigger.dev@latest install-mcp` again — it will detect and fix common issues
+ 3. Check that `npx trigger.dev@latest mcp` runs without errors in your terminal
+ 4. See the [MCP introduction](/mcp-introduction) for client-specific config details
+
+
+
+ All three if possible. If you can only pick one:
+ - **Agent Rules** if you want the broadest code generation improvement
+ - **Skills** if you use multiple AI tools and want a single install
+ - **MCP Server** if you need to trigger tasks, deploy, and search docs from your AI
+
+
+
+
+## Next steps
+
+
+
+ Install and configure the MCP Server for live project interaction.
- Portable instruction sets that teach any AI coding assistant Trigger.dev best practices for writing tasks, configs, and more.
-
- ```bash
- npx skills add triggerdotdev/skills
- ```
+ Portable instruction sets for any AI coding assistant.
+
+
+ Comprehensive rule sets installed into your AI client.
+
+
+ Learn the task patterns your AI assistant will follow.
diff --git a/docs/docs.json b/docs/docs.json
index 324052905ac..4ec2fafc0eb 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -49,9 +49,10 @@
"building-with-ai",
{
"group": "MCP Server",
- "pages": ["mcp-introduction", "mcp-tools", "mcp-agent-rules"]
+ "pages": ["mcp-introduction", "mcp-tools"]
},
- "skills"
+ "skills",
+ "mcp-agent-rules"
]
},
{
diff --git a/docs/mcp-agent-rules.mdx b/docs/mcp-agent-rules.mdx
index 321f312a842..d9ba021b891 100644
--- a/docs/mcp-agent-rules.mdx
+++ b/docs/mcp-agent-rules.mdx
@@ -1,13 +1,17 @@
---
title: "Agent rules"
sidebarTitle: "Agent rules"
-description: "Learn how to use the Trigger.dev agent rules with the MCP server"
+description: "Install Trigger.dev agent rules to guide AI assistants toward correct, up-to-date code patterns."
---
## What are Trigger.dev agent rules?
Trigger.dev agent rules are comprehensive instruction sets that guide AI assistants to write optimal Trigger.dev code. These rules ensure your AI assistant understands best practices, current APIs, and recommended patterns when working with Trigger.dev projects.
+
+ Agent Rules are one of three AI tools we provide. You can also install [Skills](/skills) for portable cross-editor instruction sets or the [MCP Server](/mcp-introduction) for live project interaction. See the [comparison table](/building-with-ai#skills-vs-agent-rules-vs-mcp) for details.
+
+
## Installation
Install the agent rules with the following command:
@@ -112,6 +116,17 @@ npx trigger.dev@latest install-rules
## Next steps
-- [Install the MCP server](/mcp-introduction) for complete Trigger.dev integration
-- [Explore MCP tools](/mcp-tools) for project management and task execution
-
+
+
+ Portable instruction sets that work across all AI coding assistants.
+
+
+ Give your AI assistant direct access to Trigger.dev tools and APIs.
+
+
+ See all AI tools and how they compare.
+
+
+ Learn the task patterns that agent rules teach your AI assistant.
+
+
diff --git a/docs/mcp-introduction.mdx b/docs/mcp-introduction.mdx
index 257522d5792..a00f3dda896 100644
--- a/docs/mcp-introduction.mdx
+++ b/docs/mcp-introduction.mdx
@@ -361,4 +361,14 @@ Once installed, you can start using the MCP server by asking your AI assistant q
## Next Steps
-- [Explore available MCP tools](/mcp-tools)
+
+
+ Explore all available MCP tools for managing your projects.
+
+
+ Portable instruction sets that teach AI assistants Trigger.dev patterns.
+
+
+ Install comprehensive rule sets directly into your AI client.
+
+
diff --git a/docs/mcp-tools.mdx b/docs/mcp-tools.mdx
index 058a3671ab7..fdcdb56f3d6 100644
--- a/docs/mcp-tools.mdx
+++ b/docs/mcp-tools.mdx
@@ -11,9 +11,9 @@ description: "Learn about how to use the tools available in the Trigger.dev MCP
Search the Trigger.dev documentation for guides, examples, and API references.
**Example usage:**
-- _"How do I create a scheduled task?"_
-- _"Show me webhook examples"_
-- _"What are the deployment options?"_
+- `"How do I create a scheduled task?"`
+- `"Show me webhook examples"`
+- `"What are the deployment options?"`
## Project Management Tools
@@ -22,32 +22,32 @@ Search the Trigger.dev documentation for guides, examples, and API references.
List all organizations you have access to.
**Example usage:**
-- _"What organizations do I have?"_
-- _"Show me my orgs"_
+- `"What organizations do I have?"`
+- `"Show me my orgs"`
### list_projects
List all projects in your Trigger.dev account.
**Example usage:**
-- _"What projects do I have?"_
-- _"List my Trigger.dev projects"_
+- `"What projects do I have?"`
+- `"List my Trigger.dev projects"`
### create_project_in_org
Create a new project in an organization.
**Example usage:**
-- _"Create a new project called 'my-app'"_
-- _"Set up a new Trigger.dev project"_
+- `"Create a new project called 'my-app'"`
+- `"Set up a new Trigger.dev project"`
### initialize_project
Initialize Trigger.dev in your project with automatic setup and configuration.
**Example usage:**
-- _"Set up Trigger.dev in this project"_
-- _"Add Trigger.dev to my app"_
+- `"Set up Trigger.dev in this project"`
+- `"Add Trigger.dev to my app"`
## Task Management Tools
@@ -56,17 +56,17 @@ Initialize Trigger.dev in your project with automatic setup and configuration.
Get the current worker for a project, including the worker version, SDK version, and registered tasks with their payload schemas.
**Example usage:**
-- _"What tasks are available?"_
-- _"Show me the tasks in dev"_
+- `"What tasks are available?"`
+- `"Show me the tasks in dev"`
### trigger_task
Trigger a task to run with a specific payload. You can add a delay, set tags, configure retries, choose a machine size, set a TTL, or use an idempotency key.
**Example usage:**
-- _"Run the email-notification task"_
-- _"Trigger my-task with userId 123"_
-- _"Execute the sync task in production"_
+- `"Run the email-notification task"`
+- `"Trigger my-task with userId 123"`
+- `"Execute the sync task in production"`
## Run Monitoring Tools
@@ -75,32 +75,32 @@ Trigger a task to run with a specific payload. You can add a delay, set tags, co
Get detailed information about a specific task run, including logs and status. Enable debug mode to get the full trace with all logs and spans.
**Example usage:**
-- _"Show me details for run run_abc123"_
-- _"Why did this run fail?"_
+- `"Show me details for run run_abc123"`
+- `"Why did this run fail?"`
### list_runs
List runs for a project. Filter by status, task, tags, version, machine size, or time period.
**Example usage:**
-- _"Show me recent runs"_
-- _"List failed runs from the last 7 days"_
-- _"What runs are currently executing?"_
+- `"Show me recent runs"`
+- `"List failed runs from the last 7 days"`
+- `"What runs are currently executing?"`
### wait_for_run_to_complete
Wait for a specific run to finish and return the result.
**Example usage:**
-- _"Wait for run run_abc123 to complete"_
+- `"Wait for run run_abc123 to complete"`
### cancel_run
Cancel a running or queued run.
**Example usage:**
-- _"Cancel run run_abc123"_
-- _"Stop that task"_
+- `"Cancel run run_abc123"`
+- `"Stop that task"`
## Deployment Tools
@@ -109,24 +109,24 @@ Cancel a running or queued run.
Deploy your project to staging or production.
**Example usage:**
-- _"Deploy to production"_
-- _"Deploy to staging"_
+- `"Deploy to production"`
+- `"Deploy to staging"`
### list_deploys
List deployments for a project. Filter by status or time period.
**Example usage:**
-- _"Show me recent deployments"_
-- _"What's deployed to production?"_
+- `"Show me recent deployments"`
+- `"What's deployed to production?"`
### list_preview_branches
List all preview branches in the project.
**Example usage:**
-- _"What preview branches exist?"_
-- _"Show me preview deployments"_
+- `"What preview branches exist?"`
+- `"Show me preview deployments"`
The deploy and list_preview_branches tools are not available when the MCP server is running with the `--dev-only` flag.
diff --git a/docs/skills.mdx b/docs/skills.mdx
index eb4add47952..f12c5c36eb6 100644
--- a/docs/skills.mdx
+++ b/docs/skills.mdx
@@ -7,7 +7,11 @@ tag: "new"
## What are agent skills?
-Skills are portable instruction sets that teach AI coding assistants how to use Trigger.dev effectively. Unlike vendor-specific config files (`.cursor/rules`, `CLAUDE.md`), skills use an open standard that works across all major AI assistants. For example, Cursor users and Claude Code users can get the same knowledge from a single install.
+Skills are portable instruction sets that teach AI coding assistants how to use Trigger.dev effectively. Unlike vendor-specific config files (`.cursor/rules`, `CLAUDE.md`), skills use an open standard that works across all major AI assistants. For example, Cursor users and Claude Code users can get the same knowledge from a single install.
+
+
+ Skills are one of three AI tools we provide. You can also install [Agent Rules](/mcp-agent-rules) for client-specific rule sets or the [MCP Server](/mcp-introduction) for live project interaction. See the [comparison table](/building-with-ai#skills-vs-agent-rules-vs-mcp) for details.
+
Skills are installed as directories containing a `SKILL.md` file. Each `SKILL.md` includes YAML frontmatter (name, description) and markdown instructions with patterns, examples, and best practices that AI assistants automatically discover and follow.
@@ -68,15 +72,15 @@ Skills work with any AI coding assistant that supports the [Agent Skills standar
## Next steps
+
+ Install comprehensive rule sets directly into your AI client.
+
Give your AI assistant direct access to Trigger.dev tools and APIs.
Learn the task patterns that skills teach your AI assistant.
-
- Build durable AI workflows with prompt chaining and human-in-the-loop.
-
Browse the full Agent Skills ecosystem.
From e536d35b1717033a638b4245a8e7c17c5e5ba6ab Mon Sep 17 00:00:00 2001
From: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date: Fri, 6 Feb 2026 13:56:39 +0000
Subject: [PATCH 006/225] fix(ci): fix docker image publishing and worker
builds (#3013)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
- **Fix Docker publish automation**: The `v.docker.*` tags pushed by the
release workflow using `GITHUB_TOKEN` don't trigger the publish workflow
(GitHub Actions limitation to prevent infinite loops). Added a
`workflow_call` to `publish.yml` directly from the release job so Docker
images are built automatically after npm publish. Tags are still pushed
for reference.
- **Fix worker Containerfiles**: The coordinator, docker-provider, and
kubernetes-provider builds have been failing since the superjson
vendoring change in `@trigger.dev/core` (#2949). The Containerfiles now
run `bundle-vendor` before `build:bundle` to generate the vendor files
that esbuild needs.
### Context
- Docker images on GHCR have been stuck at v4.3.0 — v4.3.1, v4.3.2,
v4.3.3 tags existed on GitHub but never triggered publish runs
- The worker builds (publish-worker) have been failing on every push to
main since Jan 30
## Test plan
- [x] Verified kubernetes-provider Containerfile builds locally with the
fix
- [x] Manually dispatched publish workflow for v4.3.1 — all jobs
succeeded
---
---
.github/workflows/release.yml | 12 +++++++++++-
apps/coordinator/Containerfile | 2 +-
apps/docker-provider/Containerfile | 2 +-
apps/kubernetes-provider/Containerfile | 2 +-
4 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index ca0f0ebf16b..3b4135ec099 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -122,7 +122,6 @@ jobs:
package_version=$(echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -r '.[0].version')
echo "package_version=${package_version}" >> "$GITHUB_OUTPUT"
- # this triggers the publish workflow for the docker images
- name: Create and push Docker tag
if: steps.changesets.outputs.published == 'true'
run: |
@@ -130,6 +129,17 @@ jobs:
git tag "v.docker.${{ steps.get_version.outputs.package_version }}"
git push origin "v.docker.${{ steps.get_version.outputs.package_version }}"
+ # Trigger Docker builds directly via workflow_call since tags pushed with
+ # GITHUB_TOKEN don't trigger other workflows (GitHub Actions limitation).
+ publish-docker:
+ name: 🐳 Publish Docker images
+ needs: release
+ if: needs.release.outputs.published == 'true'
+ uses: ./.github/workflows/publish.yml
+ secrets: inherit
+ with:
+ image_tag: v${{ needs.release.outputs.published_package_version }}
+
# The prerelease job needs to be on the same workflow file due to a limitation related to how npm verifies OIDC claims.
prerelease:
name: 🧪 Prerelease
diff --git a/apps/coordinator/Containerfile b/apps/coordinator/Containerfile
index 4e7b89e0af1..9e973675ab9 100644
--- a/apps/coordinator/Containerfile
+++ b/apps/coordinator/Containerfile
@@ -35,7 +35,7 @@ COPY --from=pruner --chown=node:node /app/out/full/ .
COPY --from=dev-deps --chown=node:node /app/ .
COPY --chown=node:node turbo.json turbo.json
-RUN pnpm run -r --filter coordinator build:bundle
+RUN pnpm run -r --filter @trigger.dev/core bundle-vendor && pnpm run -r --filter coordinator build:bundle
FROM alpine AS cri-tools
diff --git a/apps/docker-provider/Containerfile b/apps/docker-provider/Containerfile
index bea730bda80..42a7ac23092 100644
--- a/apps/docker-provider/Containerfile
+++ b/apps/docker-provider/Containerfile
@@ -31,7 +31,7 @@ COPY --from=pruner --chown=node:node /app/out/full/ .
COPY --from=dev-deps --chown=node:node /app/ .
COPY --chown=node:node turbo.json turbo.json
-RUN pnpm run -r --filter docker-provider build:bundle
+RUN pnpm run -r --filter @trigger.dev/core bundle-vendor && pnpm run -r --filter docker-provider build:bundle
FROM base AS runner
diff --git a/apps/kubernetes-provider/Containerfile b/apps/kubernetes-provider/Containerfile
index fb96304c26b..b46b9943275 100644
--- a/apps/kubernetes-provider/Containerfile
+++ b/apps/kubernetes-provider/Containerfile
@@ -31,7 +31,7 @@ COPY --from=pruner --chown=node:node /app/out/full/ .
COPY --from=dev-deps --chown=node:node /app/ .
COPY --chown=node:node turbo.json turbo.json
-RUN pnpm run -r --filter kubernetes-provider build:bundle
+RUN pnpm run -r --filter @trigger.dev/core bundle-vendor && pnpm run -r --filter kubernetes-provider build:bundle
FROM base AS runner
From 9b21f8d322c5d802ddd8cd848002c4d0b9afbb33 Mon Sep 17 00:00:00 2001
From: Oskar Otwinowski
Date: Tue, 10 Feb 2026 10:37:09 +0100
Subject: [PATCH 007/225] feat(webapp): Vercel integration (#2994)
Vercel integration
Desc + Vid coming soon
For human reviewer:
- check the db schema
- check if posthog user attribution call is correct (telemetry.server.ts
& `referralSource`)
---
---
.changeset/vercel-integration.md | 5 +
.vscode/settings.json | 3 +-
.../app/components/GitHubLoginButton.tsx | 2 -
.../environments/RegenerateApiKeyModal.tsx | 30 +-
.../integrations/VercelBuildSettings.tsx | 176 ++
.../components/integrations/VercelLogo.tsx | 12 +
.../integrations/VercelOnboardingModal.tsx | 1085 +++++++++++
.../OrganizationSettingsSideMenu.tsx | 9 +
apps/webapp/app/env.server.ts | 5 +
.../app/models/orgIntegration.server.ts | 24 +
.../app/models/vercelIntegration.server.ts | 1659 +++++++++++++++++
.../presenters/v3/ApiKeysPresenter.server.ts | 16 +
.../v3/DeploymentListPresenter.server.ts | 59 +-
.../EnvironmentVariablesPresenter.server.ts | 92 +-
.../v3/VercelSettingsPresenter.server.ts | 585 ++++++
.../route.tsx | 7 +-
.../route.tsx | 28 +-
.../route.tsx | 8 +-
.../route.tsx | 230 ++-
.../route.tsx | 162 +-
...ationSlug.settings.integrations.vercel.tsx | 375 ++++
.../route.tsx | 45 +
.../webapp/app/routes/_app.orgs.new/route.tsx | 21 +
.../api.v1.deployments.$deploymentId.ts | 12 +
....projects.$projectParam.vercel.projects.ts | 147 ++
...ojects.$projectRef.envvars.$slug.import.ts | 4 +
.../app/routes/auth.github.callback.tsx | 5 +-
.../app/routes/auth.google.callback.tsx | 5 +-
.../app/routes/confirm-basic-details.tsx | 20 +-
apps/webapp/app/routes/login._index/route.tsx | 2 +-
apps/webapp/app/routes/login.magic/route.tsx | 15 +-
apps/webapp/app/routes/login.mfa/route.tsx | 13 +-
apps/webapp/app/routes/magic.tsx | 3 +
...ents.$environmentId.regenerate-api-key.tsx | 38 +
...cts.$projectParam.env.$envParam.github.tsx | 36 +-
...cts.$projectParam.env.$envParam.vercel.tsx | 926 +++++++++
apps/webapp/app/routes/vercel.callback.ts | 78 +
apps/webapp/app/routes/vercel.configure.tsx | 52 +
apps/webapp/app/routes/vercel.connect.tsx | 170 ++
apps/webapp/app/routes/vercel.install.tsx | 73 +
apps/webapp/app/routes/vercel.onboarding.tsx | 465 +++++
apps/webapp/app/services/org.server.ts | 20 +
apps/webapp/app/services/postAuth.server.ts | 5 +-
.../app/services/referralSource.server.ts | 53 +
apps/webapp/app/services/telemetry.server.ts | 32 +-
.../app/services/vercelIntegration.server.ts | 656 +++++++
apps/webapp/app/utils/pathBuilder.ts | 24 +
.../environmentVariablesRepository.server.ts | 147 +-
.../app/v3/environmentVariables/repository.ts | 18 +
.../v3/services/alerts/deliverAlert.server.ts | 3 +-
.../services/initializeDeployment.server.ts | 1 +
apps/webapp/app/v3/vercel/index.ts | 17 +
.../app/v3/vercel/vercelOAuthState.server.ts | 40 +
.../vercel/vercelProjectIntegrationSchema.ts | 225 +++
.../webapp/app/v3/vercel/vercelUrls.server.ts | 26 +
apps/webapp/package.json | 1 +
apps/webapp/test/vercelUrls.test.ts | 56 +
.../migration.sql | 3 +
.../migration.sql | 29 +
.../migration.sql | 22 +
.../migration.sql | 9 +
.../migration.sql | 3 +
.../migration.sql | 3 +
.../database/prisma/schema.prisma | 113 +-
packages/core/src/v3/schemas/api.ts | 18 +
pnpm-lock.yaml | 335 +++-
66 files changed, 8388 insertions(+), 173 deletions(-)
create mode 100644 .changeset/vercel-integration.md
create mode 100644 apps/webapp/app/components/integrations/VercelBuildSettings.tsx
create mode 100644 apps/webapp/app/components/integrations/VercelLogo.tsx
create mode 100644 apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
create mode 100644 apps/webapp/app/models/vercelIntegration.server.ts
create mode 100644 apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
create mode 100644 apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
create mode 100644 apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
create mode 100644 apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
create mode 100644 apps/webapp/app/routes/vercel.callback.ts
create mode 100644 apps/webapp/app/routes/vercel.configure.tsx
create mode 100644 apps/webapp/app/routes/vercel.connect.tsx
create mode 100644 apps/webapp/app/routes/vercel.install.tsx
create mode 100644 apps/webapp/app/routes/vercel.onboarding.tsx
create mode 100644 apps/webapp/app/services/org.server.ts
create mode 100644 apps/webapp/app/services/referralSource.server.ts
create mode 100644 apps/webapp/app/services/vercelIntegration.server.ts
create mode 100644 apps/webapp/app/v3/vercel/index.ts
create mode 100644 apps/webapp/app/v3/vercel/vercelOAuthState.server.ts
create mode 100644 apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts
create mode 100644 apps/webapp/app/v3/vercel/vercelUrls.server.ts
create mode 100644 apps/webapp/test/vercelUrls.test.ts
create mode 100644 internal-packages/database/prisma/migrations/20260126175159_add_environment_variable_versioning/migration.sql
create mode 100644 internal-packages/database/prisma/migrations/20260129162621_add_organization_project_integration/migration.sql
create mode 100644 internal-packages/database/prisma/migrations/20260129162810_add_integration_deployment/migration.sql
create mode 100644 internal-packages/database/prisma/migrations/20260129162946_alter_tables_for_integrations_data/migration.sql
create mode 100644 internal-packages/database/prisma/migrations/20260129165555_add_organization_integration_idx/migration.sql
create mode 100644 internal-packages/database/prisma/migrations/20260129165809_add_worker_deployment_idx/migration.sql
diff --git a/.changeset/vercel-integration.md b/.changeset/vercel-integration.md
new file mode 100644
index 00000000000..8b638e36431
--- /dev/null
+++ b/.changeset/vercel-integration.md
@@ -0,0 +1,5 @@
+---
+"@trigger.dev/core": patch
+---
+
+Add Vercel integration support to API schemas: `commitSHA` and `integrationDeployments` on deployment responses, and `source` field for environment variable imports.
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 12aefeb358f..382a5ae6201 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -7,5 +7,6 @@
"packages/cli-v3/e2e": true
},
"vitest.disableWorkspaceWarning": true,
- "typescript.experimental.useTsgo": false
+ "typescript.experimental.useTsgo": true,
+ "chat.agent.maxRequests": 10000
}
diff --git a/apps/webapp/app/components/GitHubLoginButton.tsx b/apps/webapp/app/components/GitHubLoginButton.tsx
index 87238db087e..76a494927cd 100644
--- a/apps/webapp/app/components/GitHubLoginButton.tsx
+++ b/apps/webapp/app/components/GitHubLoginButton.tsx
@@ -32,8 +32,6 @@ export function OctoKitty({ className }: { className?: string }) {
baseProfile="tiny"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
- x="0px"
- y="0px"
viewBox="0 0 2350 2314.8"
xmlSpace="preserve"
fill="currentColor"
diff --git a/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx b/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx
index fb0c77ca7c7..439fd892f91 100644
--- a/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx
+++ b/apps/webapp/app/components/environments/RegenerateApiKeyModal.tsx
@@ -10,11 +10,14 @@ import { FormButtons } from "../primitives/FormButtons";
import { Input } from "../primitives/Input";
import { InputGroup } from "../primitives/InputGroup";
import { Paragraph } from "../primitives/Paragraph";
+import { CheckboxWithLabel } from "../primitives/Checkbox";
import { Spinner } from "../primitives/Spinner";
type ModalProps = {
id: string;
title: string;
+ hasVercelIntegration: boolean;
+ isDevelopment: boolean;
};
type ModalContentProps = ModalProps & {
@@ -22,7 +25,12 @@ type ModalContentProps = ModalProps & {
closeModal: () => void;
};
-export function RegenerateApiKeyModal({ id, title }: ModalProps) {
+export function RegenerateApiKeyModal({
+ id,
+ title,
+ hasVercelIntegration,
+ isDevelopment,
+}: ModalProps) {
const randomWord = generateTwoRandomWords();
const [open, setOpen] = useState(false);
return (
@@ -37,6 +45,8 @@ export function RegenerateApiKeyModal({ id, title }: ModalProps) {
setOpen(false)}
/>
@@ -45,7 +55,14 @@ export function RegenerateApiKeyModal({ id, title }: ModalProps) {
);
}
-const RegenerateApiKeyModalContent = ({ id, randomWord, title, closeModal }: ModalContentProps) => {
+const RegenerateApiKeyModalContent = ({
+ id,
+ randomWord,
+ title,
+ hasVercelIntegration,
+ isDevelopment,
+ closeModal,
+}: ModalContentProps) => {
const [confirmationText, setConfirmationText] = useState("");
const fetcher = useFetcher();
const isSubmitting = fetcher.state === "submitting";
@@ -83,6 +100,15 @@ const RegenerateApiKeyModalContent = ({ id, randomWord, title, closeModal }: Mod
onChange={(e) => setConfirmationText(e.target.value)}
/>
+ {hasVercelIntegration && !isDevelopment && (
+
+ )}
void;
+ discoverEnvVars: EnvSlug[];
+ onDiscoverEnvVarsChange: (slugs: EnvSlug[]) => void;
+ atomicBuilds: EnvSlug[];
+ onAtomicBuildsChange: (slugs: EnvSlug[]) => void;
+ envVarsConfigLink?: string;
+};
+
+export function BuildSettingsFields({
+ availableEnvSlugs,
+ pullEnvVarsBeforeBuild,
+ onPullEnvVarsChange,
+ discoverEnvVars,
+ onDiscoverEnvVarsChange,
+ atomicBuilds,
+ onAtomicBuildsChange,
+ envVarsConfigLink,
+}: BuildSettingsFieldsProps) {
+ return (
+ <>
+ {/* Pull env vars before build */}
+
+
+
+
+
+ Select which environments should pull environment variables from Vercel before each
+ build.{" "}
+ {envVarsConfigLink && (
+ <>
+ Configure which variables to pull.
+ >
+ )}
+
+
- {list?.retention?.wasClamped && (
-
- )}
{isAdmin && (
(location.search);
+ // Track whether the current fetch is a "check for new" request vs "load more"
+ const isCheckingForNewRef = useRef(false);
// Clear accumulated logs immediately when filters change (for instant visual feedback)
useEffect(() => {
@@ -410,7 +394,7 @@ function LogsList({
}
}, [selectedLogId]);
- // Append new logs when fetcher completes (with deduplication)
+ // Append/prepend new logs when fetcher completes (with deduplication)
useEffect(() => {
if (fetcher.data && fetcher.state === "idle") {
// Ignore fetcher data if it was loaded for a different filter state
@@ -418,14 +402,25 @@ function LogsList({
return;
}
- const existingIds = new Set(accumulatedLogs.map((log) => log.id));
- const newLogs = fetcher.data.logs.filter((log) => !existingIds.has(log.id));
- if (newLogs.length > 0) {
- setAccumulatedLogs((prev) => [...prev, ...newLogs]);
+ if (isCheckingForNewRef.current) {
+ // "Check for new" - prepend new logs, don't update cursor
+ setAccumulatedLogs((prev) => {
+ const existingIds = new Set(prev.map((log) => log.id));
+ const newLogs = fetcher.data!.logs.filter((log) => !existingIds.has(log.id));
+ return newLogs.length > 0 ? [...newLogs, ...prev] : prev;
+ });
+ isCheckingForNewRef.current = false;
+ } else {
+ // "Load more" - append logs and update cursor
+ setAccumulatedLogs((prev) => {
+ const existingIds = new Set(prev.map((log) => log.id));
+ const newLogs = fetcher.data!.logs.filter((log) => !existingIds.has(log.id));
+ return newLogs.length > 0 ? [...prev, ...newLogs] : prev;
+ });
+ setNextCursor(fetcher.data.pagination.next);
}
- setNextCursor(fetcher.data.pagination.next);
}
- }, [fetcher.data, fetcher.state, accumulatedLogs, location.search]);
+ }, [fetcher.data, fetcher.state, location.search]);
// Build resource URL for loading more
const loadMoreUrl = useMemo(() => {
@@ -477,6 +472,18 @@ function LogsList({
updateUrlWithLog(undefined);
}, [updateUrlWithLog, startTransition]);
+ const handleCheckForMore = useCallback(() => {
+ if (fetcher.state !== "idle") return;
+ // Fetch without cursor to check for new logs
+ const resourcePath = `/resources${location.pathname}`;
+ const params = new URLSearchParams(location.search);
+ params.delete("cursor");
+ params.delete("log");
+ fetcherFilterStateRef.current = location.search;
+ isCheckingForNewRef.current = true;
+ fetcher.load(`${resourcePath}?${params.toString()}`);
+ }, [fetcher, location.pathname, location.search]);
+
return (
@@ -488,6 +495,7 @@ function LogsList({
isLoadingMore={fetcher.state === "loading"}
hasMore={!!nextCursor}
onLoadMore={handleLoadMore}
+ onCheckForMore={handleCheckForMore}
selectedLogId={selectedLogId}
onLogSelect={handleLogSelect}
/>
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
index 1ffd128b308..e02d29b95b5 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
@@ -1822,7 +1822,7 @@ function PreviousRunButton({ to }: { to: string | null }) {
leadingIconClassName="size-3 group-hover/button:text-text-bright transition-colors"
className={cn("flex size-6 max-w-6 items-center", !to && "cursor-not-allowed opacity-50")}
onClick={(e) => !to && e.preventDefault()}
- shortcut={{ key: "[" }}
+ shortcut={{ key: "j" }}
tooltip="Previous Run"
disabled={!to}
replace
@@ -1841,7 +1841,7 @@ function NextRunButton({ to }: { to: string | null }) {
leadingIconClassName="size-3 group-hover/button:text-text-bright transition-colors"
className={cn("flex size-6 max-w-6 items-center", !to && "cursor-not-allowed opacity-50")}
onClick={(e) => !to && e.preventDefault()}
- shortcut={{ key: "]" }}
+ shortcut={{ key: "k" }}
tooltip="Next Run"
disabled={!to}
replace
diff --git a/apps/webapp/app/routes/resources.timezone.ts b/apps/webapp/app/routes/resources.timezone.ts
new file mode 100644
index 00000000000..f06b44e6149
--- /dev/null
+++ b/apps/webapp/app/routes/resources.timezone.ts
@@ -0,0 +1,43 @@
+import { type ActionFunctionArgs, json } from "@remix-run/server-runtime";
+import { z } from "zod";
+import {
+ setTimezonePreference,
+ uiPreferencesStorage,
+} from "~/services/preferences/uiPreferences.server";
+
+const schema = z.object({
+ timezone: z.string().min(1).max(100),
+});
+
+// Cache the supported timezones to avoid repeated calls
+const supportedTimezones = new Set(Intl.supportedValuesOf("timeZone"));
+
+export async function action({ request }: ActionFunctionArgs) {
+ let data: unknown;
+ try {
+ data = await request.json();
+ } catch {
+ return json({ success: false, error: "Invalid JSON" }, { status: 400 });
+ }
+
+ const result = schema.safeParse(data);
+
+ if (!result.success) {
+ return json({ success: false, error: "Invalid timezone" }, { status: 400 });
+ }
+
+ if (!supportedTimezones.has(result.data.timezone)) {
+ return json({ success: false, error: "Invalid timezone" }, { status: 400 });
+ }
+
+ const session = await setTimezonePreference(result.data.timezone, request);
+
+ return json(
+ { success: true },
+ {
+ headers: {
+ "Set-Cookie": await uiPreferencesStorage.commitSession(session),
+ },
+ }
+ );
+}
diff --git a/apps/webapp/app/services/preferences/uiPreferences.server.ts b/apps/webapp/app/services/preferences/uiPreferences.server.ts
index 0d23a546c2d..44282499db3 100644
--- a/apps/webapp/app/services/preferences/uiPreferences.server.ts
+++ b/apps/webapp/app/services/preferences/uiPreferences.server.ts
@@ -42,3 +42,15 @@ export async function setRootOnlyFilterPreference(rootOnly: boolean, request: Re
session.set("rootOnly", rootOnly);
return session;
}
+
+export async function getTimezonePreference(request: Request): Promise {
+ const session = await getUiPreferencesSession(request);
+ const timezone = session.get("timezone");
+ return typeof timezone === "string" ? timezone : "UTC";
+}
+
+export async function setTimezonePreference(timezone: string, request: Request) {
+ const session = await getUiPreferencesSession(request);
+ session.set("timezone", timezone);
+ return session;
+}
From bc63edd6bf4e142c5fa677cb7fd2fb4e9fe786db Mon Sep 17 00:00:00 2001
From: Eric Allam
Date: Tue, 10 Feb 2026 12:03:24 +0000
Subject: [PATCH 009/225] chore(repo): adopt vouch with issue based workflow
and require for PRs (#3022)
Adopting [https://github.com/mitchellh/vouch](vouch) so we can help
potential contributors by requiring a conversation before they can
submit a PR. Too many contributors have been skipping the conversation
part of contributing to an OSS repo and skipping right ahead to
submitting PRs
---
---
.github/ISSUE_TEMPLATE/vouch-request.yml | 28 +++++++++++++++++++++
.github/VOUCHED.td | 13 ++++++++++
.github/workflows/vouch-check-pr.yml | 23 +++++++++++++++++
.github/workflows/vouch-manage-by-issue.yml | 25 ++++++++++++++++++
CONTRIBUTING.md | 13 ++++++++++
5 files changed, 102 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/vouch-request.yml
create mode 100644 .github/VOUCHED.td
create mode 100644 .github/workflows/vouch-check-pr.yml
create mode 100644 .github/workflows/vouch-manage-by-issue.yml
diff --git a/.github/ISSUE_TEMPLATE/vouch-request.yml b/.github/ISSUE_TEMPLATE/vouch-request.yml
new file mode 100644
index 00000000000..9ffe04a8984
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/vouch-request.yml
@@ -0,0 +1,28 @@
+name: Vouch Request
+description: Request to be vouched as a contributor
+labels: ["vouch-request"]
+body:
+ - type: markdown
+ attributes:
+ value: |
+ ## Vouch Request
+
+ We use [vouch](https://github.com/mitchellh/vouch) to manage contributor trust. PRs from unvouched users are automatically closed.
+
+ To get vouched, fill out this form. A maintainer will review your request and vouch for you by commenting on this issue.
+ - type: textarea
+ id: context
+ attributes:
+ label: Why do you want to contribute?
+ description: Tell us a bit about yourself and what you'd like to work on.
+ placeholder: "I'd like to fix a bug I found in..."
+ validations:
+ required: true
+ - type: textarea
+ id: prior-work
+ attributes:
+ label: Prior contributions or relevant experience
+ description: Links to previous open source work, relevant projects, or anything that helps us understand your background.
+ placeholder: "https://github.com/..."
+ validations:
+ required: false
diff --git a/.github/VOUCHED.td b/.github/VOUCHED.td
new file mode 100644
index 00000000000..a9f276737e9
--- /dev/null
+++ b/.github/VOUCHED.td
@@ -0,0 +1,13 @@
+# Vouched contributors for Trigger.dev
+# See: https://github.com/mitchellh/vouch
+#
+# Org members
+0ski
+D-K-P
+ericallam
+matt-aitken
+mpcgrid
+myftija
+nicktrn
+samejr
+isshaddad
\ No newline at end of file
diff --git a/.github/workflows/vouch-check-pr.yml b/.github/workflows/vouch-check-pr.yml
new file mode 100644
index 00000000000..a2f4c6d1b6b
--- /dev/null
+++ b/.github/workflows/vouch-check-pr.yml
@@ -0,0 +1,23 @@
+name: Vouch - Check PR
+
+on:
+ pull_request_target:
+ types: [opened, reopened]
+
+permissions:
+ contents: read
+ pull-requests: write
+ issues: read
+
+jobs:
+ check-pr:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: mitchellh/vouch/action/check-pr@main
+ with:
+ pr-number: ${{ github.event.pull_request.number }}
+ auto-close: true
+ require-vouch: true
+ env:
+ GH_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/vouch-manage-by-issue.yml b/.github/workflows/vouch-manage-by-issue.yml
new file mode 100644
index 00000000000..36de055752f
--- /dev/null
+++ b/.github/workflows/vouch-manage-by-issue.yml
@@ -0,0 +1,25 @@
+name: Vouch - Manage by Issue
+
+on:
+ issue_comment:
+ types: [created]
+
+permissions:
+ contents: write
+ issues: write
+
+jobs:
+ manage:
+ runs-on: ubuntu-latest
+ if: >-
+ contains(github.event.comment.body, 'vouch') ||
+ contains(github.event.comment.body, 'denounce') ||
+ contains(github.event.comment.body, 'unvouch')
+ steps:
+ - uses: actions/checkout@v4
+ - uses: mitchellh/vouch/action/manage-by-issue@main
+ with:
+ comment-id: ${{ github.event.comment.id }}
+ issue-id: ${{ github.event.issue.number }}
+ env:
+ GH_TOKEN: ${{ github.token }}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0162350ffc1..fbd290f0a1d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -223,6 +223,19 @@ See the [Job Catalog](./references/job-catalog/README.md) file for more.
4. Navigate to your trigger.dev instance ([http://localhost:3030](http://localhost:3030/)), to see the jobs.
You can use the test feature to trigger them.
+## Getting vouched (required before opening a PR)
+
+We use [vouch](https://github.com/mitchellh/vouch) to manage contributor trust. **PRs from unvouched users are automatically closed.**
+
+Before you open your first pull request, you need to be vouched by a maintainer. Here's how:
+
+1. Open a [Vouch Request](https://github.com/triggerdotdev/trigger.dev/issues/new?template=vouch-request.yml) issue.
+2. Tell us what you'd like to work on and share any relevant background.
+3. A maintainer will review your request and vouch for you by commenting on the issue.
+4. Once vouched, your PRs will be accepted normally.
+
+If you're unsure whether you're already vouched, go ahead and open a PR — the check will tell you.
+
## Making a pull request
**If you get errors, be sure to fix them before committing.**
From ebffa1039ce41ce7f09ac6d558c9cc7737c70d36 Mon Sep 17 00:00:00 2001
From: DKP <8297864+D-K-P@users.noreply.github.com>
Date: Tue, 10 Feb 2026 16:47:00 +0000
Subject: [PATCH 010/225] docs: added Cursor background agent docs (#3023)
- Adds a new example project guide for running Cursor's headless CLI
agent as a Trigger.dev task with live Realtime Streams output
- New doc page at `guides/example-projects/cursor-background-agent.mdx`
- Added to sidebar nav and example projects table
---
---
docs/docs.json | 1 +
.../cursor-background-agent.mdx | 105 ++++++++++++++++++
docs/guides/introduction.mdx | 1 +
3 files changed, 107 insertions(+)
create mode 100644 docs/guides/example-projects/cursor-background-agent.mdx
diff --git a/docs/docs.json b/docs/docs.json
index 4ec2fafc0eb..41b081d90eb 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -377,6 +377,7 @@
"guides/example-projects/claude-changelog-generator",
"guides/example-projects/claude-github-wiki",
"guides/example-projects/claude-thinking-chatbot",
+ "guides/example-projects/cursor-background-agent",
"guides/example-projects/human-in-the-loop-workflow",
"guides/example-projects/mastra-agents-with-memory",
"guides/example-projects/meme-generator-human-in-the-loop",
diff --git a/docs/guides/example-projects/cursor-background-agent.mdx b/docs/guides/example-projects/cursor-background-agent.mdx
new file mode 100644
index 00000000000..fa906d2136f
--- /dev/null
+++ b/docs/guides/example-projects/cursor-background-agent.mdx
@@ -0,0 +1,105 @@
+---
+title: "Background Cursor agent using the Cursor CLI"
+sidebarTitle: "Cursor background agent"
+description: "Run Cursor's headless CLI agent in a Trigger.dev task and stream the live output to the frontend using Trigger.dev Realtime Streams."
+---
+
+import RealtimeLearnMore from "/snippets/realtime-learn-more.mdx";
+
+## Overview
+
+This example runs [Cursor's headless CLI](https://cursor.com/cli) in a Trigger.dev task. The agent spawns as a child process, and its NDJSON stdout is parsed and piped to the browser in real-time using [Realtime Streams](/realtime/react-hooks/streams). The result is a live terminal UI that renders each Cursor event (system messages, assistant responses, tool calls, results) as it happens.
+
+**Tech stack:**
+
+- **[Next.js](https://nextjs.org/)** for the web app (App Router with server actions)
+- **[Cursor CLI](https://cursor.com/cli)** for the headless AI coding agent
+- **[Trigger.dev](https://trigger.dev)** for task orchestration, real-time streaming, and deployment
+
+## Video
+
+
+
+**Features:**
+
+- **Build extensions**: Installs the `cursor-agent` binary into the task container image using `addLayer`, demonstrating how to ship system binaries with your tasks
+- **Realtime Streams v2**: NDJSON from a child process stdout is parsed and piped directly to the browser using `streams.define()` and `.pipe()`
+- **Live terminal rendering**: Each Cursor event renders as a distinct row with auto-scroll
+- **Long-running tasks**: Cursor agent runs for minutes; Trigger.dev handles lifecycle, timeouts, and retries automatically
+- **Machine selection**: Uses the `medium-2x` preset for resource-intensive CLI tools
+- **LLM model picker**: Switch between models from the UI before triggering a run
+
+## GitHub repo
+
+
+ Click here to view the full code for this project in our examples repository on GitHub. You can
+ fork it and use it as a starting point for your own project.
+
+
+## How it works
+
+### Task orchestration
+
+The task spawns the Cursor CLI as a child process and streams its output to the frontend:
+
+1. A Next.js server action triggers the `cursor-agent` task with the user's prompt and selected model
+2. The task spawns the Cursor CLI binary using a helper that returns a typed NDJSON stream and a `waitUntilExit()` promise
+3. Each line of NDJSON stdout is parsed into typed Cursor events and piped to a Realtime Stream
+4. The frontend subscribes to the stream using `useRealtimeRunWithStreams` and renders each event in a terminal UI
+5. The task waits for the CLI process to exit and returns the result
+
+### Build extension for system binaries
+
+The example includes a custom build extension that installs the `cursor-agent` binary into the container image using `addLayer`. At runtime, the binary is copied to `/tmp` and given execute permissions; this is a workaround needed when the container runtime strips execute permissions from added layers.
+
+```ts extensions/cursor-cli.ts
+export const cursorCli = defineExtension({
+ name: "cursor-cli",
+ onBuildComplete(params) {
+ params.addLayer({
+ id: "cursor-cli",
+ image: {
+ instructions: [
+ `COPY cursor-agent /usr/local/bin/cursor-agent`,
+ `RUN chmod +x /usr/local/bin/cursor-agent`,
+ ],
+ },
+ });
+ },
+});
+```
+
+### Streaming with Realtime Streams v2
+
+The stream is defined with a typed schema and piped from the child process:
+
+```ts trigger/cursor-stream.ts
+export const cursorStream = streams.define("cursor", cursorEventSchema);
+```
+
+```ts trigger/cursor-agent.ts
+const { stream, waitUntilExit } = spawnCursorAgent({ prompt, model });
+cursorStream.pipe(stream);
+await waitUntilExit();
+```
+
+On the frontend, the `useRealtimeRunWithStreams` hook subscribes to these events and renders them as they arrive.
+
+## Relevant code
+
+- **Build extension + spawn helper**: [extensions/cursor-cli.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/extensions/cursor-cli.ts): installs the binary and provides a typed NDJSON stream with `waitUntilExit()`
+- **Task definition**: [trigger/cursor-agent.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/trigger/cursor-agent.ts): spawns the CLI, pipes the stream, waits for exit
+- **Stream definition**: [trigger/cursor-stream.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/trigger/cursor-stream.ts): Realtime Streams v2 stream with typed schema
+- **Terminal UI**: [components/terminal.tsx](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/components/terminal.tsx): renders live events using `useRealtimeRunWithStreams`
+- **Event types**: [lib/cursor-events.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/lib/cursor-events.ts): TypeScript types and parsers for Cursor NDJSON events
+- **Trigger config**: [trigger.config.ts](https://github.com/triggerdotdev/examples/blob/main/cursor-cli-demo/trigger.config.ts): project config with the cursor CLI build extension
+
+
diff --git a/docs/guides/introduction.mdx b/docs/guides/introduction.mdx
index fec3242029b..116c8539b0d 100644
--- a/docs/guides/introduction.mdx
+++ b/docs/guides/introduction.mdx
@@ -56,6 +56,7 @@ Example projects are full projects with example repos you can fork and use. Thes
| [Claude changelog generator](/guides/example-projects/claude-changelog-generator) | Automatically generate professional changelogs from git commits using Claude. | — | [View the repo](https://github.com/triggerdotdev/examples/tree/main/changelog-generator) |
| [Claude GitHub wiki agent](/guides/example-projects/claude-github-wiki) | Generate and maintain GitHub wiki documentation with Claude-powered analysis. | — | [View the repo](https://github.com/triggerdotdev/examples/tree/main/claude-agent-github-wiki) |
| [Claude thinking chatbot](/guides/example-projects/claude-thinking-chatbot) | Use Vercel's AI SDK and Anthropic's Claude 3.7 model to create a thinking chatbot. | Next.js | [View the repo](https://github.com/triggerdotdev/examples/tree/main/claude-thinking-chatbot) |
+| [Cursor background agent](/guides/example-projects/cursor-background-agent) | Run Cursor's headless CLI agent as a background task, streaming live output to the browser. | Next.js | [View the repo](https://github.com/triggerdotdev/examples/tree/main/cursor-cli-demo) |
| [Human-in-the-loop workflow](/guides/example-projects/human-in-the-loop-workflow) | Create audio summaries of newspaper articles using a human-in-the-loop workflow built with ReactFlow and Trigger.dev waitpoint tokens. | Next.js | [View the repo](https://github.com/triggerdotdev/examples/tree/main/article-summary-workflow) |
| [Mastra agents with memory](/guides/example-projects/mastra-agents-with-memory) | Use Mastra to create a weather agent that can collect live weather data and generate clothing recommendations. | — | [View the repo](https://github.com/triggerdotdev/examples/tree/main/mastra-agents) |
| [OpenAI Agents SDK for Python guardrails](/guides/example-projects/openai-agent-sdk-guardrails) | Use the OpenAI Agents SDK for Python to create a guardrails system for your AI agents. | — | [View the repo](https://github.com/triggerdotdev/examples/tree/main/openai-agent-sdk-guardrails-examples) |
From 48a96efbdc22cac090c8b23ed2542b5c4f85cd42 Mon Sep 17 00:00:00 2001
From: Oskar Otwinowski
Date: Tue, 10 Feb 2026 18:30:56 +0100
Subject: [PATCH 011/225] chore(webapp): Expose Vercel errors (#3025)
---
.../v3/VercelSettingsPresenter.server.ts | 14 ++++++++++++++
...projects.$projectParam.env.$envParam.vercel.tsx | 2 ++
2 files changed, 16 insertions(+)
diff --git a/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts b/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
index d92fdbf7f7a..26688d41fdd 100644
--- a/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
+++ b/apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
@@ -25,6 +25,7 @@ export type VercelSettingsResult = {
enabled: boolean;
hasOrgIntegration: boolean;
authInvalid?: boolean;
+ authError?: string;
connectedProject?: {
id: string;
vercelProjectId: string;
@@ -52,6 +53,7 @@ export type VercelOnboardingData = {
availableProjects: VercelAvailableProject[];
hasProjectSelected: boolean;
authInvalid?: boolean;
+ authError?: string;
existingVariables: Record; // Environment slugs (non-archived only)
gitHubAppInstallations: GitHubAppInstallation[];
isGitHubConnected: boolean;
@@ -98,6 +100,7 @@ export class VercelSettingsPresenter extends BasePresenter {
enabled: true,
hasOrgIntegration: false,
authInvalid: true,
+ authError: orgIntegrationResult.error instanceof Error ? orgIntegrationResult.error.message : "Failed to fetch organization integration",
connectedProject: undefined,
isGitHubConnected: false,
hasStagingEnvironment: false,
@@ -116,6 +119,7 @@ export class VercelSettingsPresenter extends BasePresenter {
enabled: true,
hasOrgIntegration: true,
authInvalid: true,
+ authError: tokenResult.isErr() ? tokenResult.error.message : "Vercel token is invalid",
connectedProject: undefined,
isGitHubConnected: false,
hasStagingEnvironment: false,
@@ -382,6 +386,7 @@ export class VercelSettingsPresenter extends BasePresenter {
availableProjects: [],
hasProjectSelected: false,
authInvalid: true,
+ authError: tokenResult.isErr() ? tokenResult.error.message : "Vercel token is invalid",
existingVariables: {},
gitHubAppInstallations,
isGitHubConnected,
@@ -397,6 +402,7 @@ export class VercelSettingsPresenter extends BasePresenter {
availableProjects: [],
hasProjectSelected: false,
authInvalid: clientResult.error.authInvalid,
+ authError: clientResult.error.authInvalid ? clientResult.error.message : undefined,
existingVariables: {},
gitHubAppInstallations,
isGitHubConnected,
@@ -426,6 +432,7 @@ export class VercelSettingsPresenter extends BasePresenter {
availableProjects: [],
hasProjectSelected: false,
authInvalid: availableProjectsResult.error.authInvalid,
+ authError: availableProjectsResult.error.authInvalid ? availableProjectsResult.error.message : undefined,
existingVariables: {},
gitHubAppInstallations,
isGitHubConnected,
@@ -472,12 +479,19 @@ export class VercelSettingsPresenter extends BasePresenter {
(sharedEnvVarsResult.isErr() && sharedEnvVarsResult.error.authInvalid);
if (authInvalid) {
+ const authError =
+ (customEnvironmentsResult.isErr() && customEnvironmentsResult.error.authInvalid && customEnvironmentsResult.error.message) ||
+ (projectEnvVarsResult.isErr() && projectEnvVarsResult.error.authInvalid && projectEnvVarsResult.error.message) ||
+ (sharedEnvVarsResult.isErr() && sharedEnvVarsResult.error.authInvalid && sharedEnvVarsResult.error.message) ||
+ undefined;
+
return {
customEnvironments: [],
environmentVariables: [],
availableProjects: availableProjectsResult.value,
hasProjectSelected: true,
authInvalid: true,
+ authError: authError || undefined,
existingVariables: {},
gitHubAppInstallations,
isGitHubConnected,
diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
index c25f99b0554..bb0fca6d745 100644
--- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
+++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
@@ -188,10 +188,12 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
}
const authInvalid = onboardingData?.authInvalid || result.authInvalid || false;
+ const authError = onboardingData?.authError || result.authError;
return typedjson({
...result,
authInvalid,
+ authError,
onboardingData,
organizationSlug,
projectSlug: projectParam,
From 2feecece880bfb727bb8e5592e1016388a6d91b0 Mon Sep 17 00:00:00 2001
From: Saadi Myftija
Date: Tue, 10 Feb 2026 19:44:44 +0100
Subject: [PATCH 012/225] fix(api): skip external build creation for native
builds (#3024)
Native builds don't use depot, but the `/deployments/:id/progress`
endpoint was unconditionally generating depot build tokens. This is now
fixed.
The initialize deployment endpoint was already doing this check.
---
---
.../app/v3/services/deployment.server.ts | 28 ++++++++++++-------
1 file changed, 18 insertions(+), 10 deletions(-)
diff --git a/apps/webapp/app/v3/services/deployment.server.ts b/apps/webapp/app/v3/services/deployment.server.ts
index 11d659ab221..848c06c4537 100644
--- a/apps/webapp/app/v3/services/deployment.server.ts
+++ b/apps/webapp/app/v3/services/deployment.server.ts
@@ -2,7 +2,7 @@ import { type AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { BaseService } from "./baseService.server";
import { errAsync, fromPromise, okAsync, type ResultAsync } from "neverthrow";
import { type WorkerDeployment, type Project } from "@trigger.dev/database";
-import { logger, type GitMeta, type DeploymentEvent } from "@trigger.dev/core/v3";
+import { BuildServerMetadata, logger, type GitMeta, type DeploymentEvent } from "@trigger.dev/core/v3";
import { TimeoutDeploymentService } from "./timeoutDeployment.server";
import { env } from "~/env.server";
import { createRemoteImageBuild } from "../remoteImageBuilder.server";
@@ -40,7 +40,7 @@ export class DeploymentService extends BaseService {
friendlyId: string,
updates: Partial & { git: GitMeta }>
) {
- const validateDeployment = (deployment: Pick) => {
+ const validateDeployment = (deployment: Pick & { buildServerMetadata?: BuildServerMetadata }) => {
if (deployment.status !== "PENDING" && deployment.status !== "INSTALLING") {
logger.warn(
"Attempted progressing deployment that is not in PENDING or INSTALLING status",
@@ -75,14 +75,17 @@ export class DeploymentService extends BaseService {
return okAsync({ id: deployment.id, status: "INSTALLING" as const });
});
- const createRemoteBuild = (deployment: Pick) =>
- fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({
- type: "failed_to_create_remote_build" as const,
- cause: error,
- }));
+ const progressToBuilding = (
+ deployment: Pick & { buildServerMetadata?: BuildServerMetadata }
+ ) => {
+ const createRemoteBuildIfNeeded = deployment.buildServerMetadata?.isNativeBuild
+ ? okAsync(undefined)
+ : fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({
+ type: "failed_to_create_remote_build" as const,
+ cause: error,
+ }));
- const progressToBuilding = (deployment: Pick) =>
- createRemoteBuild(deployment)
+ return createRemoteBuildIfNeeded
.andThen((externalBuildData) =>
fromPromise(
this._prisma.workerDeployment.updateMany({
@@ -106,6 +109,7 @@ export class DeploymentService extends BaseService {
}
return okAsync({ id: deployment.id, status: "BUILDING" as const });
});
+ };
const extendTimeout = (deployment: Pick) =>
fromPromise(
@@ -432,6 +436,7 @@ export class DeploymentService extends BaseService {
select: {
status: true,
id: true,
+ buildServerMetadata: true,
imageReference: true,
shortCode: true,
environment: {
@@ -454,6 +459,9 @@ export class DeploymentService extends BaseService {
return errAsync({ type: "deployment_not_found" as const });
}
return okAsync(deployment);
- });
+ }).map((deployment) => ({
+ ...deployment,
+ buildServerMetadata: BuildServerMetadata.safeParse(deployment.buildServerMetadata).data,
+ }));
}
}
From ddeb9c415ed2aeb25432da28a4d78c5942f29d5b Mon Sep 17 00:00:00 2001
From: Iss <74388823+isshaddad@users.noreply.github.com>
Date: Tue, 10 Feb 2026 16:53:41 -0500
Subject: [PATCH 013/225] docs: heartbeats, Bun version, troubleshooting, and
preview-branch cleanup (#3026)
Doc updates:
- new Heartbeats page (yield, progress, external updates)
- Bun supported-version note
- resource_exhausted troubleshooting with native builder link
- GitHub Actions preview-branch example with closed trigger so branches
archive when PRs close
---
---
docs/deployment/preview-branches.mdx | 2 +-
docs/docs.json | 1 +
docs/github-actions.mdx | 35 +++++++++++++++++++++++++
docs/guides/frameworks/bun.mdx | 4 +++
docs/runs/heartbeats.mdx | 38 ++++++++++++++++++++++++++++
docs/troubleshooting.mdx | 6 ++++-
6 files changed, 84 insertions(+), 2 deletions(-)
create mode 100644 docs/runs/heartbeats.mdx
diff --git a/docs/deployment/preview-branches.mdx b/docs/deployment/preview-branches.mdx
index 7e98e512876..f2a354e2e9d 100644
--- a/docs/deployment/preview-branches.mdx
+++ b/docs/deployment/preview-branches.mdx
@@ -72,7 +72,7 @@ This GitHub Action will:
1. Automatically create a preview branch for your Pull Request (if the branch doesn't already exist).
2. Deploy the preview branch.
-3. Archive the preview branch when the Pull Request is merged/closed.
+3. Archive the preview branch when the Pull Request is merged/closed. This only works if your workflow runs on **closed** PRs (`types: [opened, synchronize, reopened, closed]`). If you omit `closed`, branches won't be archived automatically.
```yml .github/workflows/trigger-preview-branches.yml
name: Deploy to Trigger.dev (preview branches)
diff --git a/docs/docs.json b/docs/docs.json
index 41b081d90eb..5c2bddede0c 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -70,6 +70,7 @@
"machines",
"idempotency",
"runs/max-duration",
+ "runs/heartbeats",
"tags",
"runs/metadata",
"tasks/streams",
diff --git a/docs/github-actions.mdx b/docs/github-actions.mdx
index 217d8baa73c..3f1c145926f 100644
--- a/docs/github-actions.mdx
+++ b/docs/github-actions.mdx
@@ -83,6 +83,41 @@ jobs:
If you already have a GitHub action file, you can just add the final step "🚀 Deploy Trigger.dev" to your existing file.
+## Preview branches
+
+To deploy to preview branches from Pull Requests and have them archived when PRs are merged or closed, use a workflow that runs on `pull_request` with **all four types** including `closed`:
+
+```yaml .github/workflows/trigger-preview-branches.yml
+name: Deploy to Trigger.dev (preview branches)
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, closed]
+
+jobs:
+ deploy-preview:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Use Node.js 20.x
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20.x"
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Deploy preview branch
+ run: npx trigger.dev@latest deploy --env preview
+ env:
+ TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
+```
+
+
+ **Include `closed`** in the `pull_request.types` list. Without it, preview branches won't be archived when PRs are merged or closed, and you may hit the limit on active preview branches. See [Preview branches](/deployment/preview-branches#preview-branches-with-github-actions-recommended) for more details.
+
+
## Creating a Personal Access Token
diff --git a/docs/guides/frameworks/bun.mdx b/docs/guides/frameworks/bun.mdx
index e5f4ab1cd0d..d4115138250 100644
--- a/docs/guides/frameworks/bun.mdx
+++ b/docs/guides/frameworks/bun.mdx
@@ -14,6 +14,10 @@ import CliViewRunStep from "/snippets/step-view-run.mdx";
Bun will still be used to execute your tasks, even in the `dev` environment.
+
+ **Supported Bun version:** Deployed tasks run on Bun 1.3.3. For local development, use Bun 1.3.x for compatibility.
+
+
## Known issues
diff --git a/docs/runs/heartbeats.mdx b/docs/runs/heartbeats.mdx
new file mode 100644
index 00000000000..b28f9fcbde7
--- /dev/null
+++ b/docs/runs/heartbeats.mdx
@@ -0,0 +1,38 @@
+---
+title: "Heartbeats"
+sidebarTitle: "Heartbeats"
+description: "Keep long-running or CPU-heavy tasks from being marked as stalled."
+---
+
+We send a heartbeat from your task to the platform every 30 seconds. If we don't receive a heartbeat within 5 minutes, we mark the run as stalled and stop it with a `TASK_RUN_STALLED_EXECUTING` error.
+
+Code that blocks the event loop for too long (for example, a tight loop doing synchronous work on a large dataset) can prevent heartbeats from being sent. In that case, use `heartbeats.yield()` inside the loop so the runtime can yield to the event loop and send a heartbeat. You can call it every iteration; the implementation only yields when needed.
+
+```ts
+import { task, heartbeats } from "@trigger.dev/sdk";
+
+export const processLargeDataset = task({
+ id: "process-large-dataset",
+ run: async (payload: { items: string[] }) => {
+ for (const row of payload.items) {
+ await heartbeats.yield();
+ processRow(row);
+ }
+ return { processed: payload.items.length };
+ },
+});
+
+function processRow(row: string) {
+ // synchronous CPU-heavy work
+}
+```
+
+If you see `TASK_RUN_STALLED_EXECUTING`, see [Task run stalled executing](/troubleshooting#task-run-stalled-executing) in the troubleshooting guide.
+
+## Sending progress to Trigger.dev
+
+To stream progress or status updates to the dashboard and your app, use [run metadata](/runs/metadata). Call `metadata.set()` (or `metadata.append()`) as the task runs. The dashboard and [Realtime](/realtime) (including `runs.subscribeToRun` and the React hooks) receive those updates as they happen. See [Progress monitoring](/realtime/backend/subscribe#progress-monitoring) for a full example.
+
+## Sending updates to your own system
+
+Trigger.dev doesn’t push run updates to external services. To send progress or heartbeats to your own backend (for example Supabase Realtime), call your API or client from inside the task when you want to emit an update—e.g. in the same loop where you call `heartbeats.yield()` or `metadata.set()`. Use whatever your stack supports: HTTP, the Supabase client, or another SDK.
diff --git a/docs/troubleshooting.mdx b/docs/troubleshooting.mdx
index 7a003194fa7..13d9216f863 100644
--- a/docs/troubleshooting.mdx
+++ b/docs/troubleshooting.mdx
@@ -73,6 +73,10 @@ This happens because Docker Desktop left behind a config file that's still tryin
Usually there will be some useful guidance below this message. If you can't figure out what's going wrong then join [our Discord](https://trigger.dev/discord) and create a Help forum post with a link to your deployment.
+### `resource_exhausted`
+
+If you see a `resource_exhausted` error during deploy, the build may have hit resource limits on our build infrastructure. Try our [native builder](https://trigger.dev/changelog/deployments-with-native-builds).
+
### `No loader is configured for ".node" files`
This happens because `.node` files are native code and can't be bundled like other packages. To fix this, add your package to [`build.external`](/config/config-file#external) in the `trigger.config.ts` file like this:
@@ -175,7 +179,7 @@ The most common situation this happens is if you're using `Promise.all` around s
Make sure that you always use `await` when you call `trigger`, `triggerAndWait`, `batchTrigger`, and `batchTriggerAndWait`. If you don't then it's likely the task(s) won't be triggered because the calling function process can be terminated before the networks calls are sent.
-### `COULD_NOT_FIND_EXECUTOR`
+### `COULD_NOT_FIND_EXECUTOR`
If you see a `COULD_NOT_FIND_EXECUTOR` error when triggering a task, it may be caused by dynamically importing the child task. When tasks are dynamically imported, the executor may not be properly registered.
From 170fde3498f87d59f3091cecf90edd99c0f63e55 Mon Sep 17 00:00:00 2001
From: Matt Aitken
Date: Wed, 11 Feb 2026 10:44:14 +0000
Subject: [PATCH 014/225] Move vouch requirement to top of CONTRIBUTING.md
(#3029)
Contributors need to be vouched before opening PRs, but this requirement
was buried far down in the document. This change:
- Adds mention of vouches in the intro paragraph
- Moves the "Getting vouched" section to right after the intro
This makes the requirement more visible to new contributors.
Slack thread:
https://triggerdotdev.slack.com/archives/C0A7Q6F62NS/p1770805895370749
https://claude.ai/code/session_01G6VVbgfUAeCpJfedELdqq1
---
Co-authored-by: Claude
---
CONTRIBUTING.md | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fbd290f0a1d..754ad017ba9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,10 +2,23 @@
Thank you for taking the time to contribute to Trigger.dev. Your involvement is not just welcomed, but we encourage it! 🚀
-Please take some time to read this guide to understand contributing best practices for Trigger.dev.
+Please take some time to read this guide to understand contributing best practices for Trigger.dev. Note that we use [vouch](https://github.com/mitchellh/vouch) to manage contributor trust, so you'll need to be vouched before opening a PR.
Thank you for helping us make Trigger.dev even better! 🤩
+## Getting vouched (required before opening a PR)
+
+We use [vouch](https://github.com/mitchellh/vouch) to manage contributor trust. **PRs from unvouched users are automatically closed.**
+
+Before you open your first pull request, you need to be vouched by a maintainer. Here's how:
+
+1. Open a [Vouch Request](https://github.com/triggerdotdev/trigger.dev/issues/new?template=vouch-request.yml) issue.
+2. Tell us what you'd like to work on and share any relevant background.
+3. A maintainer will review your request and vouch for you by commenting on the issue.
+4. Once vouched, your PRs will be accepted normally.
+
+If you're unsure whether you're already vouched, go ahead and open a PR — the check will tell you.
+
## Developing
The development branch is `main`. This is the branch that all pull
@@ -223,19 +236,6 @@ See the [Job Catalog](./references/job-catalog/README.md) file for more.
4. Navigate to your trigger.dev instance ([http://localhost:3030](http://localhost:3030/)), to see the jobs.
You can use the test feature to trigger them.
-## Getting vouched (required before opening a PR)
-
-We use [vouch](https://github.com/mitchellh/vouch) to manage contributor trust. **PRs from unvouched users are automatically closed.**
-
-Before you open your first pull request, you need to be vouched by a maintainer. Here's how:
-
-1. Open a [Vouch Request](https://github.com/triggerdotdev/trigger.dev/issues/new?template=vouch-request.yml) issue.
-2. Tell us what you'd like to work on and share any relevant background.
-3. A maintainer will review your request and vouch for you by commenting on the issue.
-4. Once vouched, your PRs will be accepted normally.
-
-If you're unsure whether you're already vouched, go ahead and open a PR — the check will tell you.
-
## Making a pull request
**If you get errors, be sure to fix them before committing.**
From 6e3ac8bd9154aff5203d7402d6238c6f6fe3a850 Mon Sep 17 00:00:00 2001
From: DKP <8297864+D-K-P@users.noreply.github.com>
Date: Wed, 11 Feb 2026 13:32:49 +0000
Subject: [PATCH 015/225] docs: cursor cli docs update (remove chmod
workaround) (#3031)
---
---
.../cursor-background-agent.mdx | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/docs/guides/example-projects/cursor-background-agent.mdx b/docs/guides/example-projects/cursor-background-agent.mdx
index fa906d2136f..b05ffa0df9d 100644
--- a/docs/guides/example-projects/cursor-background-agent.mdx
+++ b/docs/guides/example-projects/cursor-background-agent.mdx
@@ -58,18 +58,24 @@ The task spawns the Cursor CLI as a child process and streams its output to the
### Build extension for system binaries
-The example includes a custom build extension that installs the `cursor-agent` binary into the container image using `addLayer`. At runtime, the binary is copied to `/tmp` and given execute permissions; this is a workaround needed when the container runtime strips execute permissions from added layers.
+The example includes a custom build extension that installs `cursor-agent` into the container image using `addLayer`. The official install script is run at build time, then the resolved entry point and its dependencies are copied to a fixed path so the task can invoke them at runtime with the bundled Node binary.
```ts extensions/cursor-cli.ts
-export const cursorCli = defineExtension({
+const CURSOR_AGENT_DIR = "/usr/local/lib/cursor-agent";
+
+export const cursorCli = (): BuildExtension => ({
name: "cursor-cli",
- onBuildComplete(params) {
- params.addLayer({
+ onBuildComplete(context) {
+ if (context.target === "dev") return;
+
+ context.addLayer({
id: "cursor-cli",
image: {
instructions: [
- `COPY cursor-agent /usr/local/bin/cursor-agent`,
- `RUN chmod +x /usr/local/bin/cursor-agent`,
+ "RUN apt-get update && apt-get install -y curl ca-certificates && rm -rf /var/lib/apt/lists/*",
+ 'ENV PATH="/root/.local/bin:$PATH"',
+ "RUN curl -fsSL https://cursor.com/install | bash",
+ `RUN cp -r $(dirname $(readlink -f /root/.local/bin/cursor-agent)) ${CURSOR_AGENT_DIR}`,
],
},
});
From d7bc37fdc0f90264384e1f360cbb9f997a1d8788 Mon Sep 17 00:00:00 2001
From: James Ritchie
Date: Thu, 12 Feb 2026 08:58:21 +0000
Subject: [PATCH 016/225] Feat(dashboard): show the Betterstack incident title
in the dashboard (#3006)
When the incident panel is displayed, show the title added to
BetterStack as the contents of the incident panel.
I've also brightened the UI so it's more visible.
---
---
.../navigation/HelpAndFeedbackPopover.tsx | 2 +-
.../webapp/app/routes/resources.incidents.tsx | 121 ++++++------
.../betterstack/betterstack.server.ts | 178 +++++++++++++-----
3 files changed, 195 insertions(+), 106 deletions(-)
diff --git a/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx b/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
index 74077eed724..1626ec9f910 100644
--- a/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
+++ b/apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx
@@ -59,7 +59,7 @@ export function HelpAndFeedback({
button={
diff --git a/apps/webapp/app/routes/resources.incidents.tsx b/apps/webapp/app/routes/resources.incidents.tsx
index 532038d4f99..445c3ef912a 100644
--- a/apps/webapp/app/routes/resources.incidents.tsx
+++ b/apps/webapp/app/routes/resources.incidents.tsx
@@ -1,58 +1,87 @@
import { ExclamationTriangleIcon } from "@heroicons/react/20/solid";
import { json } from "@remix-run/node";
-import { useFetcher } from "@remix-run/react";
+import { useFetcher, type ShouldRevalidateFunction } from "@remix-run/react";
import { motion } from "framer-motion";
-import { useCallback, useEffect } from "react";
+import { useEffect, useRef } from "react";
import { LinkButton } from "~/components/primitives/Buttons";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Popover, PopoverContent, PopoverTrigger } from "~/components/primitives/Popover";
import { SimpleTooltip } from "~/components/primitives/Tooltip";
import { useFeatures } from "~/hooks/useFeatures";
-import { BetterStackClient } from "~/services/betterstack/betterstack.server";
+import { BetterStackClient, type AggregateState } from "~/services/betterstack/betterstack.server";
+
+// Prevent Remix from revalidating this route when other fetchers submit
+export const shouldRevalidate: ShouldRevalidateFunction = () => false;
+
+export type IncidentLoaderData = {
+ status: AggregateState;
+ title: string | null;
+};
export async function loader() {
const client = new BetterStackClient();
- const result = await client.getIncidents();
+ const result = await client.getIncidentStatus();
if (!result.success) {
- return json({ operational: true });
+ return json({ status: "operational", title: null });
}
- return json({
- operational: result.data.attributes.aggregate_state === "operational",
+ return json({
+ status: result.data.status,
+ title: result.data.title,
});
}
-export function IncidentStatusPanel({ isCollapsed = false }: { isCollapsed?: boolean }) {
+const DEFAULT_MESSAGE =
+ "Our team is working on resolving the issue. Check our status page for more information.";
+
+const POLL_INTERVAL_MS = 60_000;
+
+/** Hook to fetch and poll incident status */
+export function useIncidentStatus() {
const { isManagedCloud } = useFeatures();
const fetcher = useFetcher();
-
- const fetchIncidents = useCallback(() => {
- if (fetcher.state === "idle") {
- fetcher.load("/resources/incidents");
- }
- }, []);
+ const hasInitiallyFetched = useRef(false);
useEffect(() => {
if (!isManagedCloud) return;
- fetchIncidents();
+ // Initial fetch on mount
+ if (!hasInitiallyFetched.current && fetcher.state === "idle") {
+ hasInitiallyFetched.current = true;
+ fetcher.load("/resources/incidents");
+ }
- const interval = setInterval(fetchIncidents, 60 * 1000); // 1 minute
+ // Poll every 60 seconds
+ const interval = setInterval(() => {
+ if (fetcher.state === "idle") {
+ fetcher.load("/resources/incidents");
+ }
+ }, POLL_INTERVAL_MS);
return () => clearInterval(interval);
- }, [isManagedCloud, fetchIncidents]);
+ }, [isManagedCloud]);
+
+ return {
+ status: fetcher.data?.status ?? "operational",
+ title: fetcher.data?.title ?? null,
+ hasIncident: (fetcher.data?.status ?? "operational") !== "operational",
+ isManagedCloud,
+ };
+}
- const operational = fetcher.data?.operational ?? true;
+export function IncidentStatusPanel({ isCollapsed = false }: { isCollapsed?: boolean }) {
+ const { title, hasIncident, isManagedCloud } = useIncidentStatus();
- if (!isManagedCloud || operational) {
+ if (!isManagedCloud || !hasIncident) {
return null;
}
+ const message = title || DEFAULT_MESSAGE;
+
return (
-
- {/* Description */}
-
- Our team is working on resolving the issue. Check our status page for more
- information.
-
-
- {/* Button */}
-
- View status page
-
-