diff --git a/docs/deployment/preview-branches.mdx b/docs/deployment/preview-branches.mdx
index 7e98e51287..f2a354e2e9 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 41b081d90e..5c2bddede0 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 217d8baa73..3f1c145926 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 e5f4ab1cd0..d411513825 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 0000000000..b28f9fcbde
--- /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 7a003194fa..13d9216f86 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.