Skip to content

Commit 6ac9a2d

Browse files
yulman2207claude
andcommitted
fix(cli): allow selecting Bun 1.4 runtime
Add `bun-1.3` and `bun-1.4` as runtime options, mapped to the published `triggerdotdev/bun:1.3-bookworm` and `1.4-bookworm` images (Bun 1.3.14 and 1.4.2). Bun 1.3.x aborts any fetch that receives no bytes for 300s, which fails long non-streaming calls; Bun 1.4 does not have that limit. Plain `bun` is unchanged and still resolves to the 1.3-node20 image. The versioned images have no node binary, so their final stage starts the app with `dumb-init bun`. The supervisor pins uid 1001 for every bun runtime. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XW9yYwuPcYpqPr1zX5pk2W
1 parent c801170 commit 6ac9a2d

15 files changed

Lines changed: 177 additions & 20 deletions

File tree

‎.changeset/bun-version-runtimes.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@trigger.dev/core": patch
3+
"@trigger.dev/rsc": patch
4+
"trigger.dev": patch
5+
---
6+
7+
Add `bun-1.3` (Bun 1.3.14) and `bun-1.4` (Bun 1.4.2) as `runtime` options in `trigger.config.ts`. Bun 1.3.x aborts any `fetch` that receives no bytes for 300 seconds, which fails long non-streaming calls such as LLM requests. Bun 1.4 does not have this limit, so set `runtime: "bun-1.4"` for those tasks. `runtime: "bun"` is unchanged and still deploys on Bun 1.3.3.
8+
9+
```ts
10+
import { defineConfig } from "@trigger.dev/sdk";
11+
12+
export default defineConfig({
13+
runtime: "bun-1.4",
14+
project: "<your-project-ref>",
15+
});
16+
```

‎apps/supervisor/src/workloadManager/kubernetes.test.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ describe("runnerSecurityContext", () => {
177177
});
178178

179179
it("pins bun's own uid, which differs from node's", () => {
180-
expect(runnerSecurityContext("restricted", 1000, "bun")?.runAsUser).toBe(1001);
180+
for (const runtime of ["bun", "bun-1.3", "bun-1.4"]) {
181+
expect(runnerSecurityContext("restricted", 1000, runtime)?.runAsUser).toBe(1001);
182+
}
181183
});
182184

183185
it("falls back to the configured uid when the runtime is unknown", () => {

‎apps/supervisor/src/workloadManager/kubernetesPodSpec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export function runnerSecurityContext(
122122
allowPrivilegeEscalation: false,
123123
capabilities: { drop: ["ALL"] },
124124
...(level === "restricted"
125-
? { runAsNonRoot: true, runAsUser: runtime === "bun" ? BUN_RUN_AS_USER : runAsUser }
125+
? { runAsNonRoot: true, runAsUser: runtime?.startsWith("bun") ? BUN_RUN_AS_USER : runAsUser }
126126
: {}),
127127
};
128128
}

‎docs/config/config-file.mdx‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,31 @@ See our [Bun guide](/guides/frameworks/bun) for more information.
347347

348348
<NodeVersions />
349349

350+
### Bun versions
351+
352+
`runtime: "bun"` deploys on Bun `1.3.3`. To pick a Bun release line yourself, use `bun-1.3` (Bun `1.3.14`) or `bun-1.4` (Bun `1.4.2`):
353+
354+
```ts trigger.config.ts
355+
import { defineConfig } from "@trigger.dev/sdk";
356+
357+
export default defineConfig({
358+
project: "<project ref>",
359+
// Your other config settings...
360+
runtime: "bun-1.4",
361+
});
362+
```
363+
364+
<Warning>
365+
On Bun 1.3.x (`bun` and `bun-1.3`), `fetch` aborts any request that receives no bytes for 300
366+
seconds with `TimeoutError: The operation timed out.`, even if you set a longer `timeout` or
367+
`AbortSignal`. A long, non-streaming call, such as an LLM request that returns nothing until it
368+
finishes, fails at exactly 5 minutes. Bun 1.4 does not have this limit, so use `bun-1.4` for
369+
tasks that make such calls.
370+
</Warning>
371+
372+
The `bun-1.3` and `bun-1.4` images don't include Node.js, so a task that spawns a `node` process
373+
needs to stay on `runtime: "bun"`, whose image does.
374+
350375
## Default machine
351376

352377
You can specify the default machine for all tasks in your project:

‎docs/guides/frameworks/bun.mdx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ import CliViewRunStep from "/snippets/step-view-run.mdx";
1515
</Warning>
1616

1717
<Note>
18-
**Supported Bun version:** Deployed tasks run on Bun 1.3.3. For local development, use Bun 1.3.x
19-
for compatibility.
18+
**Supported Bun version:** With `runtime: "bun"`, deployed tasks run on Bun 1.3.3. To deploy on a
19+
newer release, set `runtime: "bun-1.4"` (Bun 1.4.2) or `runtime: "bun-1.3"` (Bun 1.3.14), see
20+
[Bun versions](/config/config-file#bun-versions). For local development, use the same Bun release
21+
line as your deployed runtime.
2022
</Note>
2123

2224
<Prerequisites framework="Bun" />
2325

2426
## Known issues
2527

28+
- On Bun 1.3.x (`runtime: "bun"` and `"bun-1.3"`), `fetch` aborts any request that receives no bytes for 300 seconds with `TimeoutError: The operation timed out.`, so a long, non-streaming call such as an LLM request fails at exactly 5 minutes. **Workaround:** set `runtime: "bun-1.4"` in `trigger.config.ts`, which does not have this limit.
2629
- Certain OpenTelemetry instrumentation will not work with Bun, because Bun does not support Node's `register` hook. This means that some libraries that rely on this hook will not work with Bun.
2730
- If Bun is installed via Homebrew (e.g. `/opt/homebrew/bin/bun`), you may see an `ENOENT: spawn /Users/<you>/.bun/bin/bun` error because the CLI expects Bun at the default install path. **Workaround:** create a symlink:
2831
```bash

‎docs/self-hosting/overview.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,15 @@ Deploys build on the published `triggerdotdev/node` and `triggerdotdev/bun` Debi
108108
DEPLOY_BASE_IMAGES="node-26=registry.example.com/node-fips:26@sha256:<64-character-digest>"
109109
```
110110

111-
Entries are comma-separated `runtime=image@sha256:<digest>`. The runtimes are `node-22`, `node-24`, `node-26` and `bun`, and every image must be pinned by digest. An invalid value prevents the webapp from starting. Projects with `runtime: "node"` in their config resolve to the current default Node runtime (`node-24` today), so set that key for them. With the Helm chart, set the variables through `webapp.extraEnvVars`.
111+
Entries are comma-separated `runtime=image@sha256:<digest>`. The runtimes are `node-22`, `node-24`, `node-26`, `bun`, `bun-1.3` and `bun-1.4`, and every image must be pinned by digest. An invalid value prevents the webapp from starting. Projects with `runtime: "node"` in their config resolve to the current default Node runtime (`node-24` today), so set that key for them. With the Helm chart, set the variables through `webapp.extraEnvVars`.
112112

113113
Runtimes with an entry build on that image; the others keep the published images. Deploys from CLI versions that cannot apply the images are rejected with an error asking to upgrade, and so are `--native-build`, `--local-bundle` and `--from-bundle` deploys. This is a self-hosting setting and does not apply to Trigger.dev Cloud.
114114

115115
You own a custom base image. It must provide:
116116

117-
- `node` on `PATH` at the runtime's major version (for `bun`, both `bun` and `node`, because the final stage starts the app with `dumb-init node`)
117+
- `node` on `PATH` at the runtime's major version (for `bun`, both `bun` and `node`, because the final stage starts the app with `dumb-init node`; for `bun-1.3` and `bun-1.4`, only `bun`, because the final stage starts the app with `dumb-init bun`)
118118
- `busybox`, `ca-certificates`, `dumb-init`, `git` and `openssl`
119-
- a `node` user (a `bun` user for the Bun runtime)
119+
- a `node` user (a `bun` user for the Bun runtimes)
120120
- glibc, so native modules built in the build stage load at runtime
121121

122122
A `DEPLOY_BUILD_BASE_IMAGES` image needs everything the base image provides, plus `python3`, `make` and `g++`. Build extensions that add image instructions are replayed on it. Without an entry, the build stage uses the published build image, except for projects whose build extensions add image instructions: those build from your base image and install the toolchain with `apt-get`, so that base must be Debian-based. To avoid the published images entirely, set both variables.

‎docs/snippets/node-versions.mdx‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@ Trigger.dev runs your deployed tasks on one of these runtimes:
55
- Node.js `node-24` — `24.18.0`
66
- Node.js `node-26` — `26.4.0`
77
- Bun `bun` — `1.3.3`
8+
- Bun `bun-1.3` — `1.3.14`
9+
- Bun `bun-1.4` — `1.4.2`
810

911
If you don't set a `runtime`, or you set `runtime: "node"`, your tasks run on the current Node.js LTS. That version moves forward as new LTS releases land, so set an explicit version — for example `runtime: "node-24"` — if you need it to stay fixed.
1012

1113
```ts
1214
import { defineConfig } from "@trigger.dev/sdk";
1315

1416
export default defineConfig({
15-
// "node", "node-22", "node-24", "node-26" or "bun"
17+
// "node", "node-22", "node-24", "node-26", "bun", "bun-1.3" or "bun-1.4"
1618
runtime: "node-24",
1719
project: "<your-project-ref>",
1820
});

‎packages/cli-v3/src/build/plugins.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as esbuild from "esbuild";
22
import { BuildTarget } from "@trigger.dev/core/v3/schemas";
3-
import { ResolvedConfig } from "@trigger.dev/core/v3/build";
3+
import { isBunRuntime, ResolvedConfig } from "@trigger.dev/core/v3/build";
44
import { configPlugin } from "../config.js";
55
import { logger } from "../utilities/logger.js";
66
import { bunPlugin } from "../runtimes/bun.js";
@@ -25,7 +25,7 @@ export async function buildPlugins(
2525

2626
plugins.push(polyshedPlugin());
2727

28-
if (resolvedConfig.runtime === "bun") {
28+
if (isBunRuntime(resolvedConfig.runtime)) {
2929
plugins.push(bunPlugin());
3030
}
3131

‎packages/cli-v3/src/config.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { ResolvedConfig } from "@trigger.dev/core/v3/build";
99
import {
1010
DEFAULT_RUNTIME,
1111
deprecatedRuntimeReplacement,
12+
isBunRuntime,
1213
isDeprecatedConfigRuntime,
1314
resolveBuildRuntime,
1415
} from "@trigger.dev/core/v3/build";
@@ -367,10 +368,10 @@ function validateConfig(config: TriggerConfig, warn = true) {
367368
);
368369
}
369370

370-
if (config.runtime && config.runtime === "bun") {
371+
if (isBunRuntime(config.runtime)) {
371372
warn &&
372373
prettyWarning(
373-
`The "bun" runtime is currently experimental, and certain features may not work, especially opentelemetry instrumentation of 3rd party packages.`
374+
`The "${config.runtime}" runtime is currently experimental, and certain features may not work, especially opentelemetry instrumentation of 3rd party packages.`
374375
);
375376
}
376377
}

‎packages/cli-v3/src/deploy/buildImage.test.ts‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ const images: Array<[BuildRuntime, string, string]> = [
5454
"triggerdotdev/bun:1.3-node20-bookworm@sha256:25b467196277b9d75a37773ee36d28b65ca81a6f41786f7d7d7f1fad95fb5a31",
5555
"triggerdotdev/bun:1.3-node20-bookworm-build@sha256:a2d5e6d1ec25946ca1d86abdd9ffb9c589376df64ee1b490c461607953931245",
5656
],
57+
[
58+
"bun-1.3",
59+
"triggerdotdev/bun:1.3-bookworm@sha256:991ebbc955a793aaebfa619f9cd000d0c9d370a3b8673b705c8364d23a756717",
60+
"triggerdotdev/bun:1.3-bookworm-build@sha256:4d90ab4b25ae3bd2f4eda468d94a475b2b73977c5be1497df17b18384cf5ea2d",
61+
],
62+
[
63+
"bun-1.4",
64+
"triggerdotdev/bun:1.4-bookworm@sha256:8d3fdda4309808b9e12e388a4da20dcac72e67449a277b38d7aacc7c50b8e586",
65+
"triggerdotdev/bun:1.4-bookworm-build@sha256:ae31fe34246e8b8b9725a8c5366c842ccce59742a0e3a57b42f43f1b251e84a9",
66+
],
5767
];
5868

5969
describe("generateContainerfile", () => {
@@ -269,6 +279,27 @@ describe("generateContainerfile", () => {
269279
}
270280
});
271281

282+
it.each([
283+
["bun", "node"],
284+
["bun-1.3", "bun"],
285+
["bun-1.4", "bun"],
286+
] as [BuildRuntime, string][])(
287+
"starts the %s app with %s, since only plain bun ships a node binary",
288+
async (runtime, binary) => {
289+
const containerfile = await generateContainerfile({
290+
runtime,
291+
build: {},
292+
image: undefined,
293+
indexScript: "index.js",
294+
entrypoint: "entrypoint.js",
295+
});
296+
297+
expect(containerfile).toContain(`ENTRYPOINT [ "dumb-init", "${binary}", "entrypoint.js" ]`);
298+
expect(containerfile).toContain('RUN [ "bun", "run", "index.js" ]');
299+
expect(containerfile).toContain("USER bun");
300+
}
301+
);
302+
272303
it("pairs every runtime image with its -build variant", () => {
273304
for (const runtime of Object.keys(BASE_IMAGE) as BuildRuntime[]) {
274305
const baseRef = BASE_IMAGE[runtime].split("@")[0];

0 commit comments

Comments
 (0)