1982 Commits

Author SHA1 Message Date
Chris Arderne 38bf82aebe feat(cli,webapp): target notifications by minimum CLI version (#4407) 2026-07-28 14:23:41 +01:00
github-actions[bot] d189ce17d3 chore: release v4.5.8 (#4364)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 1s
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-supervisor (push) Has been skipped
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
## Summary
2 new features, 9 improvements, 3 bug fixes.

## Highlights

- Allow additional environment API keys to create scoped public access
tokens through the Trigger.dev API. Use server-issued public access
tokens for batch operations so environment-scoped API keys can read
batch results.
([#4387](https://github.com/triggerdotdev/trigger.dev/pull/4387))

## Improvements
- Preserve the partial assistant message when a chat turn's model stream
fails mid-response. `chat.agent` now passes the recovered partial to
`onTurnComplete`, and `chat.createSession`'s `turn.complete()` keeps it
before rethrowing, instead of dropping the streamed-so-far output.
([#4348](https://github.com/triggerdotdev/trigger.dev/pull/4348))

## Server changes

These changes affect the self-hosted Docker image and Trigger.dev Cloud:

- Favorite any dashboard page to a new Favorites section in the side
menu, and customize the sidebar by renaming favorites, hiding items, and
reordering items and sections.
([#4375](https://github.com/triggerdotdev/trigger.dev/pull/4375))
- List API endpoints now clamp the page size to a maximum of 100.
Requests asking for a larger page size return up to 100 items and keep
paginating, rather than pulling an unbounded page.
([#4360](https://github.com/triggerdotdev/trigger.dev/pull/4360))
- Organizations without billing alerts now get default spend alert
thresholds, so you're notified before usage grows unexpectedly. The
billing limit page no longer pre-selects an option before you've set a
limit and prompts you to configure one. Alert previews now update
immediately after you change your billing limit.
([#4328](https://github.com/triggerdotdev/trigger.dev/pull/4328))
- When you create a Personal Access Token, the generated token now shows
its first and last few characters instead of being fully hidden, so you
can confirm you copied the right value.
([#4363](https://github.com/triggerdotdev/trigger.dev/pull/4363))
- Add metrics to the realtime backend that measure how often a single
changed run is served to multiple subscriptions in one batch.
([#4341](https://github.com/triggerdotdev/trigger.dev/pull/4341))
- Realtime run subscriptions can now be configured to read run data
straight from the primary database, so a run's latest state is never
served from a lagging replica. Off by default; replica reads are
unchanged unless you turn it on.
([#4378](https://github.com/triggerdotdev/trigger.dev/pull/4378))
- SSO and Directory Sync are no longer restricted to Enterprise plans —
get in touch and we can turn them on for your organization whatever plan
you're on.
([#4393](https://github.com/triggerdotdev/trigger.dev/pull/4393))
- Improved supervisor observability: it now reports metrics for its
outbound requests, making failed calls to upstream services easier to
monitor.
([#4350](https://github.com/triggerdotdev/trigger.dev/pull/4350))
- The runs list on a task's page now updates live — run statuses change
and newly triggered runs appear without a manual refresh, matching the
main Runs page.
([#4377](https://github.com/triggerdotdev/trigger.dev/pull/4377))
- Speed up the Batches list page for environments with a large number of
batches, which could previously time out while loading.
([#4361](https://github.com/triggerdotdev/trigger.dev/pull/4361))
- Container startup no longer prints database and ClickHouse connection
strings (with credentials) to the logs.
([#4346](https://github.com/triggerdotdev/trigger.dev/pull/4346))
- The tasks page no longer runs two queries whose results were never
displayed, cutting wasted work on every page load and removing a source
of hidden server errors
([#4380](https://github.com/triggerdotdev/trigger.dev/pull/4380))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.8

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.8`
## trigger.dev@4.5.8

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.8`
  - `@trigger.dev/build@4.5.8`
  - `@trigger.dev/schema-to-json@4.5.8`
## @trigger.dev/core@4.5.8

### Patch Changes

- Allow additional environment API keys to create scoped public access
tokens through the Trigger.dev API. Use server-issued public access
tokens for batch operations so environment-scoped API keys can read
batch results.
([#4387](https://github.com/triggerdotdev/trigger.dev/pull/4387))
## @trigger.dev/python@4.5.8

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/sdk@4.5.8`
  - `@trigger.dev/core@4.5.8`
  - `@trigger.dev/build@4.5.8`
## @trigger.dev/react-hooks@4.5.8

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.8`
## @trigger.dev/redis-worker@4.5.8

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.8`
## @trigger.dev/rsc@4.5.8

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.8`
## @trigger.dev/schema-to-json@4.5.8

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.8`
## @trigger.dev/sdk@4.5.8

### Patch Changes

- Preserve the partial assistant message when a chat turn's model stream
fails mid-response. `chat.agent` now passes the recovered partial to
`onTurnComplete`, and `chat.createSession`'s `turn.complete()` keeps it
before rethrowing, instead of dropping the streamed-so-far output.
([#4348](https://github.com/triggerdotdev/trigger.dev/pull/4348))
- Allow additional environment API keys to create scoped public access
tokens through the Trigger.dev API. Use server-issued public access
tokens for batch operations so environment-scoped API keys can read
batch results.
([#4387](https://github.com/triggerdotdev/trigger.dev/pull/4387))
- Updated dependencies:
  - `@trigger.dev/core@4.5.8`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-27 16:40:31 +01:00
Chris Arderne efd0ee8d74 feat(core,sdk): support additional environment API keys (#4387)
## Summary

Additional environment API keys can use SDK APIs that require public
access tokens. The SDK detects the additional-key format and asks the
Trigger.dev server to mint scoped tokens instead of attempting to sign
them locally.

Root environment keys retain their existing local-signing behavior.
Trigger and batch clients also prefer server-issued tokens returned in
response headers while preserving compatibility with older servers.

## Deployment notes

This package update is safe to publish before servers expose additional
key creation. Existing root keys continue to use the current path, while
an additional key used with an older server fails with an actionable
upgrade error.
2026-07-27 15:19:27 +00:00
Matt Aitken be45cf9e61 fix(sdk): preserve partial assistant message on chat stream failure (#4348)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 5s
🚀 Publish Trigger.dev Docker / units (push) Failing after 5s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-supervisor (push) Has been skipped
🦋 Changesets PR / Create Release PR (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
⚒️ Publish Worker (v4) / build (supervisor) (push) Has been cancelled
## Summary

When a `chat.agent` (or `chat.createSession`) turn's model stream fails
mid-response (e.g. a transport timeout like `UND_ERR_BODY_TIMEOUT`), the
assistant output that already streamed was dropped: `onTurnComplete`
fired with `responseMessage: undefined`, and the manual loop's
`turn.complete()` rethrew without keeping the partial. Apps that
register `hydrateMessages` are hit hardest, since boot-time tail-replay
recovery is off by design.

This preserves the streamed-so-far assistant output while still
reporting the turn as errored, so persistence and recovery keep it.

## Scope of behavior change

Only the **error path** changes. Successful turns are unaffected: the
same chunks stream to the client in the same order, and
backpressure/cancel behave as before. Everything here is a correctness
improvement on a turn that hit a source-stream failure.

## What it does

Follow-up to #4304 (`chat.pipeAndCapture`), extending the same
partial-recovery to the two loops that lacked it:

- **`chat.agent`**: taps the response stream (via a `TransformStream`,
so pass-through backpressure and cancel are preserved) to buffer chunks,
and on a source-stream failure reconstructs the partial (preferring the
`onFinish` message). It's surfaced on the error-path `onTurnComplete`
(`responseMessage`, `rawResponseMessage`, `uiMessages`, `newUIMessages`,
`newMessages`) and committed to the accumulator so the next turn and the
reboot snapshot keep it.
- **`chat.createSession` / `turn.complete()`**: the reconstructed
partial is accumulated (so `turn.uiMessages` reflects it and the caller
can persist after catching) before `turn.complete()` rethrows.

`onBeforeTurnComplete` stays skipped on the error path (it hands out a
writer for a stream that has already broken).

## Correctness properties (each covered by a regression test)

Each test below was confirmed to fail without its fix:

- The recovered partial reaches `onTurnComplete` and the next turn's
accumulated messages.
- An already-committed (possibly enriched) response is not overwritten
if a post-response hook then throws.
- Incomplete tool parts are cleaned from the recovered partial (text
kept), so the UI and model views agree and the next turn isn't poisoned.
- A prior turn's model-only compaction survives an errored turn (append
only the new tail, don't reconvert the full history).
- A reconstructed fragment that reuses an existing message id does not
clobber the complete message.
- Queued `chat.response` data parts are folded into the recovered
partial, matching the success path.
- `newMessages` (model delta) stays symmetric with `newUIMessages`.

## Tests

New `chat-agent-source-stream-error.test.ts` covers the cases above. The
full `@trigger.dev/sdk` unit suite passes and the package build is green
across all supported runtimes (Node 20 to 26, Bun, Deno, Cloudflare
Workers).
2026-07-24 14:20:20 +01:00
nicktrn 722e240e4d feat(supervisor): add prometheus metric for outbound http requests (#4350)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 5s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-supervisor (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 6s
🦋 Changesets PR / Create Release PR (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
Adds Prometheus metrics so the supervisor's outbound HTTP calls are
observable - including client-side failures that previously only
surfaced as a log line.

- `supervisor_outbound_request_total{name, method, status, outcome}` -
counts every outbound request. `outcome` separates a transport failure
(`network_error`), an HTTP error response (`http_error`), a response
that failed schema validation (`invalid_response`), and success (`ok`).
- `supervisor_outbound_request_duration_seconds{name, outcome}` -
latency histogram. Leaner labels than the counter (no `status`) to avoid
bucket×label cardinality; buckets match the existing dequeue-latency
histogram since these calls share the same retrying HTTP client and
long-poll envelope.

Coverage:
- The warm-start request (a one-off `fetch`) - instrumented inline; the
response status code is now also included in the failure log (it was
previously dropped).
- All worker API client calls (`SupervisorHttpClient`: dequeue, run
attempt start/complete, heartbeats, snapshots, continue, suspend,
debug-log, connect) - routed through a single instrumented `request()`
helper that reports via an optional `onHttpRequestComplete` callback on
the client, which the supervisor wires into the counter + histogram.

Low cardinality by design: `name` is a **static per-endpoint label**
(e.g. `dequeue`, `start_run_attempt`), never the interpolated URL - so
no run/snapshot IDs land in labels, mirroring the templated `route`
labels on the inbound HTTP server.

Registered on the existing metrics registry, exposed on `/metrics` with
no new wiring. Internal-only change (no package release needed), so the
changelog note is a single `.server-changes` entry.
2026-07-23 18:18:58 +01:00
github-actions[bot] aafc333523 chore: release v4.5.7 (#4319)
🚀 Publish Trigger.dev Docker / units (push) Failing after 5s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 15s
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-supervisor (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
## Summary
5 improvements, 5 bug fixes.

## Improvements
- Add `node-24` and `node-26` as supported `runtime` options in
`trigger.config.ts`. The `experimental-node-24` and
`experimental-node-26` names are now deprecated aliases and emit a
deprecation warning; switch to `node-24` / `node-26` instead.
([#4337](https://github.com/triggerdotdev/trigger.dev/pull/4337))
  
  ```ts
  import { defineConfig } from "@trigger.dev/sdk";
  
  export default defineConfig({
  runtime: "node-24",
  project: "<your-project-ref>",
  });
  ```
- Avoid logging task run environment variable values at debug level
([#4336](https://github.com/triggerdotdev/trigger.dev/pull/4336))
- Custom chat agent loops get two ergonomic wins for owning the turn
loop. ([#4304](https://github.com/triggerdotdev/trigger.dev/pull/4304))
  
`chat.writeTurnComplete()` now returns the turn boundary's resume
cursors (`lastEventId` for the output stream and `sessionInEventId` for
the input stream), so you can persist them straight from the task
instead of round-tripping them back from the client.
  
  ```ts
const { lastEventId, sessionInEventId } = await
chat.writeTurnComplete();
  await db.chats.update(chatId, { lastEventId, sessionInEventId });
  ```
  
`chat.pipeAndCapture()` no longer throws when a stream is stopped or
fails. It now returns a `PipeAndCaptureResult` whose `message` holds any
partial output captured before the stop or failure, alongside a typed
`status` (`"complete" | "aborted" | "error"`) and, on failure, the
`error`. Read the message off the result:
  
  ```ts
  const { message, status, error } = await chat.pipeAndCapture(result, {
  signal,
  });
  if (message) conversation.addResponse(message);
  if (status === "error") logger.error("turn failed", { error });
  ```
  
Note: `pipeAndCapture` previously resolved to `UIMessage | undefined`.
Update call sites to read `.message` from the returned result.
- Suppress a build-time warning that could appear in Vite-based projects
when the optional `@ai-sdk/otel` package is not installed.
([#4188](https://github.com/triggerdotdev/trigger.dev/pull/4188))

## Bug fixes
- Fixes intermittent `trigger dev` run crashes where a run could fail at
boot with a cryptic `Cannot find module .../dev-run-worker.mjs` after a
rebuild had cleaned up the build directory the run was launched against.
Dev runs now retry cleanly instead of hard-crashing when their build
directory is missing, the dev watchdog no longer removes the build tree
of a still-running session, and a run assigned to a worker version that
was superseded by a rebuild now fails fast with a clear message instead
of silently hanging until it times out.
([#4276](https://github.com/triggerdotdev/trigger.dev/pull/4276))

## Server changes

These changes affect the self-hosted Docker image and Trigger.dev Cloud:

- Refreshed the side menu: separate organization and account menus, a
new project switcher, and the menu is now resizable by dragging its
edge. The account Profile page has also been redesigned.
([#4066](https://github.com/triggerdotdev/trigger.dev/pull/4066))
- Allow different organization members to use the same development
branch name without sharing or colliding with each other's branch
environments.
([#4323](https://github.com/triggerdotdev/trigger.dev/pull/4323))
- Limit account settings email input to 254 characters.
([#4330](https://github.com/triggerdotdev/trigger.dev/pull/4330))
- Prevent duplicate Staging and Preview environments when account setup
requests overlap
([#4261](https://github.com/triggerdotdev/trigger.dev/pull/4261))
- Fix the docs link on the empty Prompts page, which pointed to a page
that no longer exists.
([#4247](https://github.com/triggerdotdev/trigger.dev/pull/4247))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.7

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.7`
## trigger.dev@4.5.7

### Patch Changes

- Fixes intermittent `trigger dev` run crashes where a run could fail at
boot with a cryptic `Cannot find module .../dev-run-worker.mjs` after a
rebuild had cleaned up the build directory the run was launched against.
Dev runs now retry cleanly instead of hard-crashing when their build
directory is missing, the dev watchdog no longer removes the build tree
of a still-running session, and a run assigned to a worker version that
was superseded by a rebuild now fails fast with a clear message instead
of silently hanging until it times out.
([#4276](https://github.com/triggerdotdev/trigger.dev/pull/4276))
- Add `node-24` and `node-26` as supported `runtime` options in
`trigger.config.ts`. The `experimental-node-24` and
`experimental-node-26` names are now deprecated aliases and emit a
deprecation warning; switch to `node-24` / `node-26` instead.
([#4337](https://github.com/triggerdotdev/trigger.dev/pull/4337))

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

  export default defineConfig({
    runtime: "node-24",
    project: "<your-project-ref>",
  });
  ```

- Avoid logging task run environment variable values at debug level
([#4336](https://github.com/triggerdotdev/trigger.dev/pull/4336))
- Updated dependencies:
  - `@trigger.dev/core@4.5.7`
  - `@trigger.dev/build@4.5.7`
  - `@trigger.dev/schema-to-json@4.5.7`
## @trigger.dev/core@4.5.7

### Patch Changes

- Add `node-24` and `node-26` as supported `runtime` options in
`trigger.config.ts`. The `experimental-node-24` and
`experimental-node-26` names are now deprecated aliases and emit a
deprecation warning; switch to `node-24` / `node-26` instead.
([#4337](https://github.com/triggerdotdev/trigger.dev/pull/4337))

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

  export default defineConfig({
    runtime: "node-24",
    project: "<your-project-ref>",
  });
  ```
## @trigger.dev/python@4.5.7

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/sdk@4.5.7`
  - `@trigger.dev/core@4.5.7`
  - `@trigger.dev/build@4.5.7`
## @trigger.dev/react-hooks@4.5.7

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.7`
## @trigger.dev/redis-worker@4.5.7

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.7`
## @trigger.dev/rsc@4.5.7

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.7`
## @trigger.dev/schema-to-json@4.5.7

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.7`
## @trigger.dev/sdk@4.5.7

### Patch Changes

- Custom chat agent loops get two ergonomic wins for owning the turn
loop. ([#4304](https://github.com/triggerdotdev/trigger.dev/pull/4304))

`chat.writeTurnComplete()` now returns the turn boundary's resume
cursors (`lastEventId` for the output stream and `sessionInEventId` for
the input stream), so you can persist them straight from the task
instead of round-tripping them back from the client.

  ```ts
const { lastEventId, sessionInEventId } = await
chat.writeTurnComplete();
  await db.chats.update(chatId, { lastEventId, sessionInEventId });
  ```

`chat.pipeAndCapture()` no longer throws when a stream is stopped or
fails. It now returns a `PipeAndCaptureResult` whose `message` holds any
partial output captured before the stop or failure, alongside a typed
`status` (`"complete" | "aborted" | "error"`) and, on failure, the
`error`. Read the message off the result:

  ```ts
  const { message, status, error } = await chat.pipeAndCapture(result, {
    signal,
  });
  if (message) conversation.addResponse(message);
  if (status === "error") logger.error("turn failed", { error });
  ```

Note: `pipeAndCapture` previously resolved to `UIMessage | undefined`.
Update call sites to read `.message` from the returned result.

- Suppress a build-time warning that could appear in Vite-based projects
when the optional `@ai-sdk/otel` package is not installed.
([#4188](https://github.com/triggerdotdev/trigger.dev/pull/4188))
- Updated dependencies:
  - `@trigger.dev/core@4.5.7`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-22 15:51:29 +01:00
Chris Arderne 55a3bf2858 feat(core,cli): add node-24 and node-26 runtimes, deprecate experimental aliases (#4337)
## Summary

Adds `node-24` and `node-26` as first-class `runtime` options in
`trigger.config.ts`. Previously these Node versions were only reachable
via the `experimental-node-24` / `experimental-node-26` names.

Those experimental names are now **deprecated aliases**: they still
resolve to `node-24` / `node-26` for backwards compatibility, but
loading a config that uses them prints a deprecation warning pointing at
the new name.

```ts
export default defineConfig({
  runtime: "node-24",
  project: "<your-project-ref>",
});
```

## Details

- `ConfigRuntime` (the public config schema) now accepts `node-24` and
`node-26` directly; the internal `BuildRuntime` already supported them,
so base images and the deploy path are unchanged.
- `resolveBuildRuntime` passes the new names straight through and keeps
mapping the experimental aliases to their replacements.
- Renamed the runtime helper from `isExperimentalConfigRuntime` to
`isDeprecatedConfigRuntime` and added `deprecatedRuntimeReplacement` so
the CLI can name the replacement in its warning.
- Docs snippet updated to list the new versions and flag the deprecated
names.
2026-07-22 14:10:27 +01:00
Chris Arderne 509a4597bd fix(cli): redact task run env values from debug log (#4336) 2026-07-22 12:32:37 +00:00
Eric Allam a9815f745c fix(cli): stop dev runs crashing when a rebuild removes an in-use build dir (#4276) 2026-07-22 08:47:58 +01:00
Matt Aitken e2d3b8388c feat(sdk): return lastEventId from writeTurnComplete and typed capture result (#4304)
## Summary

Two ergonomic additions for custom chat-agent loops that own the turn
loop (`chat.customAgent`, `chat.createSession`, and the hand-rolled
primitives).

`chat.writeTurnComplete()` now resolves to `{ lastEventId }`, the resume
cursor for the start of the next turn. A custom loop can persist it
straight from the task instead of round-tripping it back from the client
after the turn ends. The value was already produced internally by the
turn-complete write; the public wrapper simply discarded it.

`chat.pipeAndCapture()` no longer throws when a stream is stopped or
fails. It now resolves to a `PipeAndCaptureResult` carrying any partial
`message` captured before the stop or failure, a typed `status`
(`"complete" | "aborted" | "error"`), and the `error` on failure.
Previously a failed stream threw and the partial was lost, and an abort
was captured only when the AI SDK happened to fire `onFinish` in time.

```ts
const { message, status, error } = await chat.pipeAndCapture(result, { signal });
if (message) conversation.addResponse(message);
if (status === "error") logger.error("turn failed", { error });

const { lastEventId } = await chat.writeTurnComplete();
await db.chats.update(chatId, { lastEventId });
```

## Design

`pipeAndCapture` wraps the pipe in a `try/catch` and classifies the
outcome from the abort signal (a stop drains the source stream cleanly
rather than throwing) versus a thrown error. It also races the
`onFinish` capture against a timeout so a hard stop that prevents
`onFinish` from firing can't hang the caller. This mirrors the capture
path `chat.agent` already uses internally.

The `finishReason` from `onFinish` is surfaced too, since it was already
captured on the built-in path.

The internal `turn.complete()` helper keeps its existing contract: it
still returns `UIMessage | undefined`, still throws on a genuine stream
failure, and still discards output on a full run cancel.

## Breaking change

`chat.pipeAndCapture` previously resolved to `UIMessage | undefined`.
Call sites now read `.message` off the result. This is a young,
low-level API; the docs examples are updated in this PR.
2026-07-21 17:08:30 +02:00
Katia Bulatova d05f1a7398 chore(webapp): migrate from Remix compiler to Vite (#4188)
Replaces Remix compiler with the Vite plugin. The Express server
(cluster, socket.io, ws) and the Docker image contract are unchanged.
2026-07-21 15:57:13 +02:00
Chris Arderne dc87b884e7 chore: upgrade to typescript 6 (#4310)
## Summary

Upgrades the workspace to TypeScript 6.0.3 and applies the compiler,
type, and build configuration changes required to preserve package
layouts and existing runtime behavior, apart from correcting the HTTP
status field used for deployment connection errors.

## Compatibility

- Centralizes TypeScript 6.0.3 through the pnpm workspace catalog.
- Replaces compiler options and module resolution modes that TypeScript
6 no longer accepts.
- Restores explicit Node types where TypeScript 6 no longer includes
them transitively.
- Adds explicit declaration build roots that preserve each package's
existing output layout.
- Patches tsup to stop injecting the removed `baseUrl` option during
declaration builds.
- Uses type-only assertions for stricter typed-array and stream
definitions without changing runtime behavior.
- Reads the EventSource v3 HTTP status from `code`, so deployment
connection errors include it correctly.
- Keeps standalone CLI compatibility fixtures pinned to their existing
TypeScript version and lockfiles.

`turbo run typecheck` and the complete PR test suite are green.
2026-07-21 13:57:52 +01:00
github-actions[bot] 325b906319 chore: release v4.5.6 (#4317)
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 20s
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-supervisor (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🧭 Helm Chart Release / release (push) Has been cancelled
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
## Summary
5 improvements, 9 bug fixes.

## Breaking changes
- Self-hosted deployments no longer ship shared default credentials;
fresh installs generate their own. If yours still uses a previously
published default, set a unique value before upgrading, or set
`ALLOW_INSECURE_DEFAULT_SECRETS=true` to keep booting while you migrate.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))

## Improvements
- Require explicit browser approval for CLI and MCP login, with
resilient polling while approval is pending.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Deployed task telemetry now reports the deployment identifier (e.g.
`deployment_abc123`) in the `worker.id` attribute, instead of an opaque
internal value. Upgrade to get the readable identifier in your own
OpenTelemetry exporters.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Prevent prototype pollution when applying run metadata operations or
reconstructing nested telemetry attributes, while preserving legitimate
`constructor` and `prototype` fields.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Add helpers to mint and verify the deployment-scoped token used to
authenticate run controllers to the platform.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))

## Server changes

These changes affect the self-hosted Docker image and Trigger.dev Cloud:

- Added optional request rate limiting for telemetry ingestion
endpoints.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Background-worker deployment lookups are now scoped to the
authenticated environment.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Updating a GitHub App installation from the callback flow is now
scoped to your own organization, so an installation ID belonging to
another organization can no longer be used to refresh that
organization's installation record. The GitHub App installation session
is also now single-use, so completing an installation callback
invalidates its state and it can no longer be replayed.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Scope schedule and environment-variable writes to the caller's project
and environment
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Reject compute snapshot callbacks that do not match the snapshot
request that created them.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Require secret-key authentication to initialize the session out
(agent→client) stream, matching the append route.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Live run and trace subscriptions now validate their identifiers more
strictly and only return data from your own organization.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Window-function names in the query compiler are now validated against
the allowlist, matching how other function calls are handled.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Authenticate run controllers to the platform with a signed,
deployment-scoped token.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Verify that worker actions (starting, completing, and continuing a
run, and reading its snapshots) target a run belonging to the caller's
environment.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.6

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.6`
## trigger.dev@4.5.6

### Patch Changes

- Require explicit browser approval for CLI and MCP login, with
resilient polling while approval is pending.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Deployed task telemetry now reports the deployment identifier (e.g.
`deployment_abc123`) in the `worker.id` attribute, instead of an opaque
internal value. Upgrade to get the readable identifier in your own
OpenTelemetry exporters.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Updated dependencies:
  - `@trigger.dev/core@4.5.6`
  - `@trigger.dev/build@4.5.6`
  - `@trigger.dev/schema-to-json@4.5.6`
## @trigger.dev/core@4.5.6

### Patch Changes

- Prevent prototype pollution when applying run metadata operations or
reconstructing nested telemetry attributes, while preserving legitimate
`constructor` and `prototype` fields.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Require explicit browser approval for CLI and MCP login, with
resilient polling while approval is pending.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
- Add helpers to mint and verify the deployment-scoped token used to
authenticate run controllers to the platform.
([#4316](https://github.com/triggerdotdev/trigger.dev/pull/4316))
## @trigger.dev/python@4.5.6

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.6`
  - `@trigger.dev/build@4.5.6`
  - `@trigger.dev/sdk@4.5.6`
## @trigger.dev/react-hooks@4.5.6

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.6`
## @trigger.dev/redis-worker@4.5.6

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.6`
## @trigger.dev/rsc@4.5.6

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.6`
## @trigger.dev/schema-to-json@4.5.6

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.6`
## @trigger.dev/sdk@4.5.6

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.6`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-21 12:07:52 +01:00
Chris Arderne 6997aeb05e fix: security release 2026-07-08 (#4316)
⚒️ Publish Worker (v4) / build (supervisor) (push) Has been cancelled
2026-07-21 12:00:58 +01:00
github-actions[bot] 1cbe25bd1d chore: release v4.5.5 (#4267)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 1s
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-supervisor (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
## Summary
5 improvements, 5 bug fixes.

## Improvements
- Add experimental Node.js 24 and 26 task runtimes. Set `runtime` to
`experimental-node-24` or `experimental-node-26` in `trigger.config.ts`.
([#4085](https://github.com/triggerdotdev/trigger.dev/pull/4085))
- Add `defaultRegion` to the project GET and list API responses; null
when unset.
([#4146](https://github.com/triggerdotdev/trigger.dev/pull/4146))

## Server changes

These changes affect the self-hosted Docker image and Trigger.dev Cloud:

- Transient internal sync failures are now retried quietly instead of
surfacing as errors.
([#4270](https://github.com/triggerdotdev/trigger.dev/pull/4270))
- Optionally route ClickHouse read traffic to a read replica while
writes stay on the primary. Set `CLICKHOUSE_READER_URL` to move all
reads, or target the busiest paths with `RUNS_LIST_CLICKHOUSE_URL` (runs
list) and `EVENTS_READER_CLICKHOUSE_URL` (traces, spans, logs). All
optional; unset keeps current behavior.
([#4081](https://github.com/triggerdotdev/trigger.dev/pull/4081))
- Remove the deprecated realtime stream write endpoint used by retired
v3 task clients.
([#4250](https://github.com/triggerdotdev/trigger.dev/pull/4250))
- Fix batchTrigger requests that set a per-item idempotency key failing
with an error instead of creating and deduplicating the runs
([#4271](https://github.com/triggerdotdev/trigger.dev/pull/4271))
- Speed up idempotency checks on `batchTrigger` calls that use
idempotency keys. Large batches against a task with a big run history no
longer degrade to multi-second lookups.
([#4255](https://github.com/triggerdotdev/trigger.dev/pull/4255))
- The "Preview branches" usage on the Limits page now counts only
preview branches.
([#4283](https://github.com/triggerdotdev/trigger.dev/pull/4283))
- Avoid opening a redundant database connection pool when the legacy and
primary databases are the same server, preventing connection usage from
doubling.
([#4253](https://github.com/triggerdotdev/trigger.dev/pull/4253))
- Fix pages occasionally loading unstyled or failing to load during a
deploy. The dashboard now reloads automatically to recover.
([#4282](https://github.com/triggerdotdev/trigger.dev/pull/4282))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.5

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.5`
## trigger.dev@4.5.5

### Patch Changes

- Add experimental Node.js 24 and 26 task runtimes. Set `runtime` to
`experimental-node-24` or `experimental-node-26` in `trigger.config.ts`.
([#4085](https://github.com/triggerdotdev/trigger.dev/pull/4085))
- Updated dependencies:
  - `@trigger.dev/core@4.5.5`
  - `@trigger.dev/build@4.5.5`
  - `@trigger.dev/schema-to-json@4.5.5`
## @trigger.dev/core@4.5.5

### Patch Changes

- Add experimental Node.js 24 and 26 task runtimes. Set `runtime` to
`experimental-node-24` or `experimental-node-26` in `trigger.config.ts`.
([#4085](https://github.com/triggerdotdev/trigger.dev/pull/4085))
- Add `defaultRegion` to the project GET and list API responses; null
when unset.
([#4146](https://github.com/triggerdotdev/trigger.dev/pull/4146))
## @trigger.dev/python@4.5.5

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.5`
  - `@trigger.dev/build@4.5.5`
  - `@trigger.dev/sdk@4.5.5`
## @trigger.dev/react-hooks@4.5.5

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.5`
## @trigger.dev/redis-worker@4.5.5

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.5`
## @trigger.dev/rsc@4.5.5

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.5`
## @trigger.dev/schema-to-json@4.5.5

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.5`
## @trigger.dev/sdk@4.5.5

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.5`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-20 14:01:37 +01:00
Daniel Sutton ae96b6c175 fix: read-your-writes + global-scope idempotency correctness under the run-ops split (#4284)
## What & why

Two related correctness fixes for the run-ops DB split. Under the split,
run-store reads can route to a **lagging read replica**; a just-written
run/waitpoint/batch can then be missed, causing a wrong decision.

**1. Read-your-writes → owning primary.** Surfaced first as an
intermittent `wait.until({ idempotencyKey })` re-wait on retry. Auditing
the run-store read surface found the same class at sibling sites (some
gating mutations or returning spurious 404s, others
tolerable/self-healing). Reads that must observe their own writes now
route to the owning **primary**
(`findRun`/`findWaitpoint`/`findBatchTaskRunByFriendlyId` →
`*OnPrimary`, a primary re-read on a miss, or a retryable 404 where the
SDK polls). Read-view reads stay on the replica. All additive — the
happy path is unchanged.

**2. Global-scope idempotency across the split.** A `global`-scope key
carries no per-run salt, so the same `(env, task, key)` triggered
concurrently from parents resident on **different** run-ops DBs could
dedup-miss on each DB and create a duplicate (the per-DB unique index
can't enforce cross-DB uniqueness). Such triggers (global scope, or
scope-absent, while split is active) are serialized through the existing
Redis idempotency claim, the loser resolves the winner by id across both
DBs, and the claim is reacquired on the expired/failed
clear-and-recreate path. `run`/`attempt` scope embed the run id and
never contend.

## Stacked for review

This is the **base** of a 2-PR stack, split so review is easier:
- **This PR** — production code only (34 files).
- **Stacked tests PR →
https://github.com/triggerdotdev/trigger.dev/pull/4285** — the
caller-driven guards (55 test files) on top of this branch.

## Validation

Local run-ops split, **both 2-DB and 3-DB**, fresh boot on this branch:
SDK canary 64/71 (only the known concurrency/input-streams/s3 failures),
quarantine sweep **0 unexpected** (340 pass / 16 known / 4 local) in
each topology, dashboard e2e 0 failed. No product regressions.
2026-07-19 17:57:41 +01:00
Chris Arderne d7ec75d5ad feat(runtime): add experimental Node.js 24 and 26 task runtimes (#4085)
⚒️ Publish Worker (v4) / build (supervisor) (push) Has been cancelled
## Summary

Adds experimental Node.js 24 and 26 task runtimes through the
`experimental-node-24` and `experimental-node-26` config values.

Existing runtime defaults and the `node`, `node-22`, and `bun` behavior
remain unchanged. The unprefixed `node-24` and `node-26` config values
remain unavailable until the runtimes are ready for general use.

## Design

Experimental config values normalize to canonical runtime identifiers
before build manifests are created, keeping deployment metadata and
execution behavior consistent. Kubernetes task pods also use the
runtime-default seccomp profile so modern Node.js versions fall back
from io_uring to checkpoint-compatible system calls.
2026-07-16 12:19:03 +01:00
Chris Arderne b902e65dfb chore: standardise internal node on 24.18.0 (#4254)
## Summary

Updates the internal development, CI, and runtime-image Node version to
24.18.0. SDK compatibility coverage continues to include Node 20, 22,
24, and 26.

The Node type definitions and the package-manager lockfiles now resolve
against Node 24 types.
2026-07-15 12:49:12 +01:00
nicktrn 976171ea16 feat(webapp): management API for orgs, projects, members, and settings (#4146)
## Summary

Adds a set of PAT-authenticated management API endpoints so orgs,
projects, members/invites, environment variables, and a few
project/environment settings can be managed programmatically (scripting,
automation) rather than only through the dashboard. Each route is a thin
wrapper over the **existing** service the dashboard already uses, with
the same authorization applied at the route layer - no new business
logic.

## Endpoints

**Organizations**
- `POST /api/v1/orgs` - create an org (`createOrganization`)
- `PATCH /api/v1/orgs/:orgParam` - rename (title)
- `DELETE /api/v1/orgs/:orgParam` - soft-delete
(`DeleteOrganizationService`; keeps the active-subscription guard)

**Members & invites**
- `GET /api/v1/orgs/:orgParam/members` - list members + pending invites
- `DELETE /api/v1/orgs/:orgParam/members/:memberId` - remove a member
(last-member guarded)
- `POST /api/v1/orgs/:orgParam/invites` - invite by email
(`inviteMembers`, sends the invite email)
- `DELETE /api/v1/orgs/:orgParam/invites/:inviteId` - revoke an invite

**Projects**
- `PATCH /api/v1/projects/:projectRef` - rename
(`ProjectSettingsService`)
- `DELETE /api/v1/projects/:projectRef` - soft-delete
(`DeleteProjectService`)
- `PUT /api/v1/projects/:projectRef/default-region` - set the default
region by worker-group name (`SetDefaultRegionService`)
- project GET/list now return `defaultRegion` (worker-group name, or
null when unset)

**Environments**
- `POST /api/v1/projects/:projectRef/:env/pause` and `/resume`
(`PauseEnvironmentService`)
- `POST /api/v1/projects/:projectRef/:env/regenerate-api-key` - rotate
the env secret key (`regenerateApiKey`, RBAC `write:apiKeys`)
- env var create now accepts an optional `isSecret` flag

## Auth & authorization

- All routes authenticate with a **Personal Access Token**
(`Authorization: Bearer tr_pat_...`).
- Org/project routes are built on the PAT route builders in
`apiBuilder.server.ts`: `createLoaderPATApiRoute` (already existed) and
**`createActionPATApiRoute`** (added here - the loader builder had no
mutation counterpart). The builder runs auth, resolves the org/project
role-floor via `context`, and enforces a declarative `authorization`
block using the same RBAC actions the dashboard applies
(`manage:organization` / `read:members` / `manage:members` /
`manage:project`). Handlers keep a membership-scoped query as the floor,
so a non-member gets a 404. This also gives these routes `tenantContext`
user attribution (Sentry) and `ServiceValidationError`-to-status mapping
for free.
- **Membership floor (important).** The OSS RBAC fallback grants a
permissive ability, so `ability.can(...)` can't reject a non-member on
self-hosted. Every handler therefore resolves the target scoped to the
caller's membership (`members: { some: { userId } }`) → 404 for
non-members. `authorization` is the *role* gate; this is the *tenant*
gate. `resolveOrganizationForApiUser`
(`organizationApiAccess.server.ts`) is the org-tier version of the
existing `findProjectByRef` - org-addressed PAT routes are new, so no
such helper existed before.
- Env-tier routes reuse the existing `authorizePatEnvironmentAccess`
(`write:apiKeys`).

### What `createActionPATApiRoute` gives you

A route is pure declaration - the builder handles auth, RBAC,
validation, tracing, and error mapping:

```ts
export const action = createActionPATApiRoute(
  {
    method: "PUT",                          // one verb, or ["PATCH", "DELETE"] for multi-verb routes
    params: ParamsSchema,
    body: SetDefaultRegionRequestBody,      // zod-validated
    context: async ({ projectRef }) => {    // resolve the org for the RBAC role-floor
      const project = await prisma.project.findFirst({
        where: { externalRef: projectRef, deletedAt: null },
        select: { organizationId: true },
      });
      return project ? { organizationId: project.organizationId } : {};
    },
    authorization: { action: "manage", resource: () => ({ type: "project" }) },
  },
  async ({ params, body, authentication, ability }) => {
    // auth + authz already enforced. Just do the work.
    // `throw new ServiceValidationError("Region not found", 400)` → mapped to that status.
    return json({ ok: true });
  }
);
```

Handled for you, so handlers stay thin:

- **Method allowlist** - `method` accepts a verb or an array; any other
verb → `405` with an `Allow` header, *before* auth runs:
  ```ts
const allowedMethods = method ? (Array.isArray(method) ? method :
[method]) : undefined;
if (allowedMethods && !(allowedMethods as
string[]).includes(request.method.toUpperCase())) {
return json({ error: "Method not allowed" }, { status: 405, headers: {
Allow: allowedMethods.join(", ") } });
  }
  ```
- **PAT / user-actor auth** in a single roundtrip → `401` on
missing/invalid/revoked token.
- **RBAC** - `context` computes the caller's role-floor for the target
org/project; `authorization` gates it → `403` with a structured error
body.
- **Sentry attribution** - `tenantContext.enrich({ userId })` so events
from the handler carry the acting user.
- **Typed errors** - a thrown `ServiceValidationError` is mapped to its
`.status` (default 400); anything else → `500`, and expected boundary
errors are logged as `warn` (kept out of Sentry).
- **Validation** - params / query / headers / body are all zod-checked →
`400` with details.

## Notes for reviewers

- Everything wraps an existing service; the intent is API parity for
things that are currently dashboard-only, not new behaviour.
- `createActionPATApiRoute` is new shared infra (the PAT + RBAC mutation
builder that didn't exist). It's self-contained - the loader builder and
existing routes are untouched.
- `@trigger.dev/core` gets one additive field (`defaultRegion` on the
project response, optional/nullable for client-server version skew) -
changeset included, patch.
- `removeTeamMember`'s last-member guard is now atomic (Serializable
transaction via the `$transaction` helper, with retry), so the dashboard
and API both get it server-side. Added a `## Transactions` rule to
`apps/webapp/CLAUDE.md` (always use the `$transaction` helper);
migrating the remaining direct usages is tracked in TRI-11698.

## Open questions

- ~~Is PAT the right auth (vs OAT for automation)?~~ **Resolved: PAT.**
Organization Access Tokens are currently internal-only (used by the
image builder) and not user-accessible, so they can't back this yet.
- Should any of these be gated behind a flag or scope?
- Naming/shape of the routes.
2026-07-15 10:20:08 +01:00
Chris Arderne 313fe03481 test(core): fix flakey run-stream test depending on ordering (#4256) 2026-07-14 12:29:57 +01:00
github-actions[bot] 165955781d chore: release v4.5.4 (#4228)
📚 Publish docs / publish (push) Has been cancelled
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 1s
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 0s
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
## Summary
2 new features, 11 improvements, 5 bug fixes.

## Breaking changes
- Trigger.dev v3 is no longer supported. For self-hosted deployments,
4.5.0 is the last version we officially support for running v3; stay on
4.5.0 or upgrade to v4. v3 triggers, batch triggers, reschedules, and
deploys now return a clear upgrade message instead of running.
([#4236](https://github.com/triggerdotdev/trigger.dev/pull/4236))

## Improvements
- You can now mark environment variables synced via the `syncEnvVars`
build extension as secrets. Return `{ name, value, isSecret: true }`
from your callback and those variables are stored redacted in the
dashboard, just like manually created secret env vars.
([#4203](https://github.com/triggerdotdev/trigger.dev/pull/4203))
- Remove the legacy `--mcp` and `--mcp-port` options from the `dev`
command. Run the dedicated `trigger mcp` command to start the
Trigger.dev MCP server.
([#4246](https://github.com/triggerdotdev/trigger.dev/pull/4246))
- Removed the unused `ResourceMonitor` export from
`@trigger.dev/core/v3/serverOnly`. It was a server-side logging helper
with no remaining consumers.
([#4244](https://github.com/triggerdotdev/trigger.dev/pull/4244))
- Removed the unused `@trigger.dev/core/v3/zodNamespace` export and the
legacy v3 socket message schemas. These were only used by the
now-retired v3 engine and have no v4 consumers.
([#4236](https://github.com/triggerdotdev/trigger.dev/pull/4236))

## Bug fixes
- Fix a `chat.agent` message-loss race where sending a message right
after an action (such as an undo) could drop the follow-up's response
from the UI until a refresh.
([#4234](https://github.com/triggerdotdev/trigger.dev/pull/4234))

## Server changes

These changes affect the self-hosted Docker image and Trigger.dev Cloud:

- Added `EVENT_REPOSITORY_POSTGRES_WRITES_DISABLED` to skip all
PostgreSQL task-event writes for deployments that store task events in
ClickHouse. Leave it off unless `EVENT_REPOSITORY_DEFAULT_STORE` is
`clickhouse_v2`, otherwise task events are lost.
([#4242](https://github.com/triggerdotdev/trigger.dev/pull/4242))
- Promo credits: a /promo signup landing page, redeeming a promo code
when a new org selects a plan, and showing remaining credits on the
usage page.
([#4138](https://github.com/triggerdotdev/trigger.dev/pull/4138))
- Speed up retrieving a background worker by version. The endpoint no
longer runs a slow lookup that scanned the full task table for large
deployments; it now reuses data it already loads, so the response is the
same but returns much faster.
([#4245](https://github.com/triggerdotdev/trigger.dev/pull/4245))
- Clearer login error when an email address is blocked by the
WHITELISTED_EMAILS setting: the message now explains the address isn't
allowed on this instance instead of the ambiguous "This email is
unauthorized".
([#4220](https://github.com/triggerdotdev/trigger.dev/pull/4220))
- Make the native build server the default in project build settings.
It's now opt-out, stored as a new `disableNativeBuildServer` key. Also
clarifies in the UI that build settings apply to GitHub-triggered and
native build server deployments.
([#3980](https://github.com/triggerdotdev/trigger.dev/pull/3980))
- Optionally process high-volume telemetry ingestion in parallel for
higher throughput under heavy load by setting
`OTEL_TRANSFORM_WORKER_POOL_ENABLED=1`. Off by default.
([#4232](https://github.com/triggerdotdev/trigger.dev/pull/4232))
- Add a `REALTIME_BACKEND_DEFAULT` env var to choose the default
realtime backend (`electric`, `native`, or `shadow`) for environments
whose org has no per-org override. Defaults to `electric`, so existing
behavior is unchanged.
([#4231](https://github.com/triggerdotdev/trigger.dev/pull/4231))
- Clarified on the Regions page that a region only affects where your
runs execute, not where your data is stored. This shows as a tooltip on
the Location column and in the confirmation dialog when you change your
default region.
([#4226](https://github.com/triggerdotdev/trigger.dev/pull/4226))
- Improved the reliability of how run data is read and written.
([#4237](https://github.com/triggerdotdev/trigger.dev/pull/4237))
- Fixed stale login errors: an error from a previous login attempt (for
example a rejected email address) no longer keeps reappearing on the
login page and no longer makes later, successful attempts look like they
failed.
([#4220](https://github.com/triggerdotdev/trigger.dev/pull/4220))
- The Errors page now shows better details for each error. Errors that
don't carry a message — such as errors thrown without a message, or
values thrown that aren't `Error` objects — get a meaningful title
instead of all reading "Unknown error", and are grouped by their name
(or value) rather than collapsed into a single group. The error type now
shows the actual error name, and stack traces now appear where
previously they were missing.
([#4225](https://github.com/triggerdotdev/trigger.dev/pull/4225))
- Return a clear client error when SSO form submissions use an
unsupported content type
([#4238](https://github.com/triggerdotdev/trigger.dev/pull/4238))
- Query page: extracting fields from a run's output with JSON functions
(such as JSONExtractString or JSONExtractInt) no longer fails with an
"illegal type: JSON" error.
([#4221](https://github.com/triggerdotdev/trigger.dev/pull/4221))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.4

### Patch Changes

- You can now mark environment variables synced via the `syncEnvVars`
build extension as secrets. Return `{ name, value, isSecret: true }`
from your callback and those variables are stored redacted in the
dashboard, just like manually created secret env vars.
([#4203](https://github.com/triggerdotdev/trigger.dev/pull/4203))
- Updated dependencies:
  - `@trigger.dev/core@4.5.4`
## trigger.dev@4.5.4

### Patch Changes

- Remove the legacy `--mcp` and `--mcp-port` options from the `dev`
command. Run the dedicated `trigger mcp` command to start the
Trigger.dev MCP server.
([#4246](https://github.com/triggerdotdev/trigger.dev/pull/4246))
- Updated dependencies:
  - `@trigger.dev/core@4.5.4`
  - `@trigger.dev/build@4.5.4`
  - `@trigger.dev/schema-to-json@4.5.4`
## @trigger.dev/core@4.5.4

### Patch Changes

- Removed the unused `ResourceMonitor` export from
`@trigger.dev/core/v3/serverOnly`. It was a server-side logging helper
with no remaining consumers.
([#4244](https://github.com/triggerdotdev/trigger.dev/pull/4244))
- Removed the unused `@trigger.dev/core/v3/zodNamespace` export and the
legacy v3 socket message schemas. These were only used by the
now-retired v3 engine and have no v4 consumers.
([#4236](https://github.com/triggerdotdev/trigger.dev/pull/4236))
## @trigger.dev/python@4.5.4

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/sdk@4.5.4`
  - `@trigger.dev/core@4.5.4`
  - `@trigger.dev/build@4.5.4`
## @trigger.dev/react-hooks@4.5.4

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.4`
## @trigger.dev/redis-worker@4.5.4

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.4`
## @trigger.dev/rsc@4.5.4

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.4`
## @trigger.dev/schema-to-json@4.5.4

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.4`
## @trigger.dev/sdk@4.5.4

### Patch Changes

- Fix a `chat.agent` message-loss race where sending a message right
after an action (such as an undo) could drop the follow-up's response
from the UI until a refresh.
([#4234](https://github.com/triggerdotdev/trigger.dev/pull/4234))
- Updated dependencies:
  - `@trigger.dev/core@4.5.4`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-14 10:07:58 +01:00
Chris Arderne 64e5d732ad chore(webapp,core): remove the unused ResourceMonitor server logging helper (#4244)
The `ResourceMonitor` server-side logging helper is no longer used. It
periodically logged the webapp process own memory, disk, and CPU usage
behind the `RESOURCE_MONITOR_ENABLED` flag (off by default), and was
also exported from `@trigger.dev/core/v3/serverOnly` with no other
consumers.

This removes the helper, its `@trigger.dev/core` export, the webapp
wiring, and the `RESOURCE_MONITOR_ENABLED` env var. The supervisor has
its own unrelated `ResourceMonitor` class, which is left untouched.
2026-07-13 20:32:29 +01:00
Chris Arderne 6e943f2421 chore(cli): remove --mcp option from trigger dev (#4246) 2026-07-13 16:23:05 +01:00
Eric Allam 5ba8557a51 chore(webapp,core): remove the end-of-life v3 (engine V1) execution stack (#4236)
## Summary

v3 (the engine that ran the SDK v3 era, internally
`RunEngineVersion.V1`) is end-of-life. Following the removal of the v3
execution apps
([#4194](https://github.com/triggerdotdev/trigger.dev/pull/4194)) and
the legacy dev websocket
([#4198](https://github.com/triggerdotdev/trigger.dev/pull/4198)), this
removes the remaining v3 execution stack from the server.

Clients still on v3 (an old SDK or CLI that has not upgraded) keep
getting a clear "upgrade to v4" response. Triggers, batch triggers,
reschedules, and deploys that resolve to v3 are rejected with a graceful
4xx pointing at the migration guide, never a 5xx, so a stale client
cannot affect server health. Self-hosted instances still running v3
should stay on the 4.5.x release line until they migrate.

## What is removed

- The MarQS queue and its shared/dev queue consumers.
- The v3 socket.io namespaces (coordinator, provider, shared-queue) and
the v3 run lifecycle services (attempt, checkpoint, and batch-resume).
- The graphile-worker background job system; all live jobs already run
on `@trigger.dev/redis-worker`.
- The `DEPRECATE_V3_ENABLED` flag: v3 is now rejected unconditionally,
so the flag is gone.
- Unused v3 exports from `@trigger.dev/core` (the `v3/zodNamespace`
subpath and the legacy socket message catalogs) and the now-dead MarQS
environment variables.

## What stays

The v4 engine is untouched. The graceful v3 rejection boundary stays,
`determineEngineVersion` still detects a v3 project so it can reject it,
and the batch service plus batch-completion worker stay for current
clients. Live queue concurrency limits and metrics now read from the v4
run engine instead of MarQS, and a brand-new dev environment now
defaults to v4.



## Dependency cleanup

Removes webapp dependencies left unused by this change: `seedrandom` and
`semver` (only the removed v3 code used them) plus a set that was
already dead, their orphaned `@types` packages, and two dead files. Adds
a `knip:deps` script and a `knip.json` config so unused dependencies can
be found the same way going forward.
2026-07-13 11:32:06 +01:00
Eric Allam 9b3a7bd7b2 fix(sdk,webapp): stop chat losing a message sent right after an action (#4234)
## Summary

Sending a chat message immediately after an action (for example an undo)
could make the message's response vanish from the UI. The transport
opened a response stream that closed on the *earlier* turn's completion
instead of waiting for the send's own turn. The agent still produced and
persisted the answer, so it reappeared on refresh. Same "disappearing
message" class as
[#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176),
different cause.

## Fix

A send's response stream had no way to tell whether a `turn-complete`
belonged to its turn. `POST /realtime/v1/sessions/:id/in/append` now
returns the appended record's sequence number, and the transport skips
any turn-complete whose `session-in-event-id` (the agent's committed
`.in` cursor) is below that seq, closing only on its own turn. Older
webapps omit the seq, in which case the transport falls back to the
previous behavior, so the SDK and server can ship independently.

Because the fix spans the SDK and the server, both a webapp deploy and
an SDK release are needed for the full effect.

Verified end to end with the ai-chat reference app:
undo-then-immediate-send loses the follow-up's answer before the fix and
streams it inline after, with a revert-the-guard run reproducing the
loss on the same script. Unit tests cover the skip and the no-seq
fallback.
2026-07-11 12:46:24 +01:00
Oskar Otwinowski 4be32d411c fix(webapp): keep the last Owner on directory-sync role changes (#4230)
Applying a directory-sync effect that would demote the org's last Owner
(a
group remap, or a provision) previously threw and 500'd the settings
save. Now
rbac.setUserRole reports code:"last_owner" and applyEffect skips just
that
member (they keep Owner) while the rest of the batch applies.

Adds the machine-readable RoleAssignmentResult.code to the plugin
contract so
callers can tell the last-owner guard apart from a real failure.
2026-07-10 19:28:16 +02:00
Matt Aitken b64b54c74e feat(webapp): pass database writer and reader config to auth plugins (#4229)
## Summary

The RBAC and SSO auth plugins can own their own database client, but
they could only read `DATABASE_URL`, so every connection they opened
landed on the primary. The host webapp now resolves writer and
read-replica URLs from its env (the same fallback chain its own Prisma
clients use: control-plane URL first, then the default) and passes them
to the plugins at create time via a shared `PluginDatabaseConfig`, along
with separate connection limits for writes (default 2) and reads
(default 5, tunable via `RBAC_DATABASE_*_CONNECTION_LIMIT` and
`SSO_DATABASE_*_CONNECTION_LIMIT`).

A plugin can then route hot-path reads (per-request auth checks, login
routing) to the read replica and keep only rare mutations on the
primary. With no replica configured, or no plugin installed, nothing
changes: the OSS fallback ignores the new option and keeps reading
through the Prisma clients it is already given.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-10 17:25:42 +01:00
Iss 983bd03131 feat: support isSecret in syncEnvVars (#4203)
## What

Adds per-variable secret support to the `syncEnvVars` build extension.
Return `{ name, value, isSecret: true }` and the variable is stored as a
secret (redacted in the dashboard, value non-revealable), just like a
manually created secret env var. Secret and non-secret variables can be
mixed in one callback.

```ts
syncEnvVars(async () => [
  { name: "PUBLIC_API_URL", value: "https://api.example.com" },
  { name: "DATABASE_URL", value: "postgres://...", isSecret: true },
]);
```

## How

Env vars flow through the build pipeline as a flat name→value map, and
the import API's `isSecret` is per-call. So secret vars are carried
through the layer + manifest in parallel `secretEnv` / `secretParentEnv`
maps, and at deploy time they go up in a second `importEnvVars` call
with `isSecret: true` (the plain vars in the first call). The record
form (`{ KEY: "value" }`) is unchanged and stays non-secret.

## Commits

- `feat(core)`: carry secret env vars through the build layer + manifest
schema
- `feat(build)`: partition `isSecret` vars in `syncEnvVars`
- `feat(cli)`: merge secret layers and import them with `isSecret: true`
at deploy
- `test(build)`: cover the partitioning + document `isSecret`

## Testing

- vitest covers the partitioning (secret/non-secret × child/parent) and
that the record form stays non-secret.
- Verified against a local webapp that the deploy's import contract
stores the secret var redacted (`isSecret: true`) and the plain var
visible.

Closes TRI-11099
2026-07-10 11:03:28 -04:00
github-actions[bot] 9f76c92021 chore: release v4.5.3 (#4219)
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 3s
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
## Summary
1 improvement, 2 bug fixes.

## Breaking changes
- Removed support for the end-of-life v3 `trigger dev` CLI. Starting a
dev session with an old v3 CLI now returns an upgrade message instead of
connecting - upgrade to the v4 CLI to continue using `trigger dev`.
([#4198](https://github.com/triggerdotdev/trigger.dev/pull/4198))

## Bug fixes
- Fix TS2742 ("inferred type cannot be named") when exporting a
`chat.agent` from a project with declaration emit: `ChatTaskWirePayload`
and `ChatInputChunk` are now declared in the public
`@trigger.dev/sdk/chat` subpath, so inferred agent types emit portable
declarations and the wire types are directly importable.
([#4218](https://github.com/triggerdotdev/trigger.dev/pull/4218))

## Server changes

These changes affect the self-hosted Docker image and Trigger.dev Cloud:

- Reduce primary database load on the runs page by serving its
empty-state check from ClickHouse instead of Postgres.
([#4202](https://github.com/triggerdotdev/trigger.dev/pull/4202))
- Fixed submitting your email on the login page reloading back to an
empty form instead of showing the magic link confirmation screen.
([#4215](https://github.com/triggerdotdev/trigger.dev/pull/4215))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.3

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.3`
## trigger.dev@4.5.3

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/build@4.5.3`
  - `@trigger.dev/core@4.5.3`
  - `@trigger.dev/schema-to-json@4.5.3`
## @trigger.dev/python@4.5.3

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/sdk@4.5.3`
  - `@trigger.dev/build@4.5.3`
  - `@trigger.dev/core@4.5.3`
## @trigger.dev/react-hooks@4.5.3

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.3`
## @trigger.dev/redis-worker@4.5.3

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.3`
## @trigger.dev/rsc@4.5.3

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.3`
## @trigger.dev/schema-to-json@4.5.3

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.3`
## @trigger.dev/sdk@4.5.3

### Patch Changes

- Fix TS2742 ("inferred type cannot be named") when exporting a
`chat.agent` from a project with declaration emit: `ChatTaskWirePayload`
and `ChatInputChunk` are now declared in the public
`@trigger.dev/sdk/chat` subpath, so inferred agent types emit portable
declarations and the wire types are directly importable.
([#4218](https://github.com/triggerdotdev/trigger.dev/pull/4218))
- Updated dependencies:
  - `@trigger.dev/core@4.5.3`
## @trigger.dev/core@4.5.3

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-10 08:42:34 +01:00
Eric Allam 25254d0201 fix(sdk): make inferred chat agent types portable for declaration emit (#4218)
## Summary

Exporting a `chat.agent` from a project with `declaration: true` failed
with TS2742: the inferred type of the agent references
`ChatTaskWirePayload`, which was declared in an internal module not
reachable through the package exports map, so tsc could only name it via
a file path into `node_modules` and refused to emit. Consumers had to
hand-mirror the wire type and annotate their export.

## Fix

`ChatTaskWirePayload` and `ChatInputChunk` are now declared in
`@trigger.dev/sdk/chat` (a public subpath) and re-exported type-only
from the internal shared module, so every internal import is unchanged
and the browser/server module split is untouched. Declaration emit for
an inferred agent type now produces a portable specifier:

```ts
export declare const chatAgent: Task<"chat-agent", import("@trigger.dev/sdk/chat").ChatTaskWirePayload<MyUIMessage, MyClientData>, unknown>;
```

As a side effect the wire types are now directly importable, which is
what affected users were reconstructing by hand.

## Verification

Reproduced against the built 4.5.2-equivalent package: a consumer
fixture with declaration emit produced `import("<file
path>/ai-shared.js")` in its declaration (the TS2742 trigger); after the
fix the same fixture emits the public specifier with zero diagnostics. A
regression test now builds that consumer simulation in a temp directory
on every test run: it copies the built package into a fake node_modules
(copied, not symlinked, because tsc only applies exports-map naming to
real node_modules paths), compiles the fixture with the TypeScript API,
and asserts no errors, no relative-path imports, and no internal module
references in the emit.
2026-07-10 07:35:03 +01:00
github-actions[bot] 188f008715 chore: release v4.5.2 (#4180)
## Summary
4 improvements, 5 bug fixes.

## Improvements
- Add SDK and API client helpers for run bulk actions.
([#4105](https://github.com/triggerdotdev/trigger.dev/pull/4105))
- Large batch payloads now offload to object storage instead of riding
inline in the trigger request. `batchTrigger` and `batchTriggerAndWait`
(and the by-id and by-task variants) offload any per-item payload over
128KB before sending, the same way single `trigger` and `triggerAndWait`
already do, so a big batch no longer blows past the API body limit.
([#4165](https://github.com/triggerdotdev/trigger.dev/pull/4165))
- Removed internal helpers that were only used by the end-of-life v3
self-hosted compute providers.
([#4194](https://github.com/triggerdotdev/trigger.dev/pull/4194))
- Add an `onEvent` callback to `TriggerChatTransport` /
`useTriggerChatTransport` that emits typed lifecycle events for sends,
stream connects, first chunk, and turn completion. Send-success metrics,
time-to-first-token, and "sent but never answered" watchdogs become a
few lines of client code.
([#4187](https://github.com/triggerdotdev/trigger.dev/pull/4187))
  
  ```ts
  onEvent: (event) => {
if (event.type === "message-sent") metrics.timing("chat.send_ms",
event.durationMs);
if (event.type === "first-chunk") metrics.timing("chat.ttft_ms",
event.sinceSendMs ?? 0);
  },
  ```

## Bug fixes
- fix(cli): honor the MCP server's `--dev-only` flag
([#4199](https://github.com/triggerdotdev/trigger.dev/pull/4199))
- Fix chat turns that throw (for example from an `onTurnStart` hook)
leaking their message listener, which lost or duplicated messages sent
during later turns.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))
- Fix `chat.agent` and `chat.createSession` permanently dropping user
messages when several arrived during a single turn: every buffered
message is now dispatched as its own turn instead of only the first.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))
- Fix chat continuation runs replaying already-answered messages: turns
delivered while the run was suspended now advance the session.in resume
cursor, so a new run picks up exactly where the previous one left off.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))
- Fix `chat.createSession` swallowing a message sent shortly after
stopping a turn: the turn's message listener now detaches when the
stream settles, so those messages run as the next turn.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.2

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
## trigger.dev@4.5.2

### Patch Changes

- fix(cli): honor the MCP server's `--dev-only` flag
([#4199](https://github.com/triggerdotdev/trigger.dev/pull/4199))
- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
  - `@trigger.dev/build@4.5.2`
  - `@trigger.dev/schema-to-json@4.5.2`
## @trigger.dev/core@4.5.2

### Patch Changes

- Add SDK and API client helpers for run bulk actions.
([#4105](https://github.com/triggerdotdev/trigger.dev/pull/4105))
- Large batch payloads now offload to object storage instead of riding
inline in the trigger request. `batchTrigger` and `batchTriggerAndWait`
(and the by-id and by-task variants) offload any per-item payload over
128KB before sending, the same way single `trigger` and `triggerAndWait`
already do, so a big batch no longer blows past the API body limit.
([#4165](https://github.com/triggerdotdev/trigger.dev/pull/4165))
- Removed internal helpers that were only used by the end-of-life v3
self-hosted compute providers.
([#4194](https://github.com/triggerdotdev/trigger.dev/pull/4194))
## @trigger.dev/python@4.5.2

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
  - `@trigger.dev/sdk@4.5.2`
  - `@trigger.dev/build@4.5.2`
## @trigger.dev/react-hooks@4.5.2

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
## @trigger.dev/redis-worker@4.5.2

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
## @trigger.dev/rsc@4.5.2

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
## @trigger.dev/schema-to-json@4.5.2

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
## @trigger.dev/sdk@4.5.2

### Patch Changes

- Add SDK and API client helpers for run bulk actions.
([#4105](https://github.com/triggerdotdev/trigger.dev/pull/4105))
- Fix chat turns that throw (for example from an `onTurnStart` hook)
leaking their message listener, which lost or duplicated messages sent
during later turns.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))
- Fix `chat.agent` and `chat.createSession` permanently dropping user
messages when several arrived during a single turn: every buffered
message is now dispatched as its own turn instead of only the first.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))
- Fix chat continuation runs replaying already-answered messages: turns
delivered while the run was suspended now advance the session.in resume
cursor, so a new run picks up exactly where the previous one left off.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))
- Fix `chat.createSession` swallowing a message sent shortly after
stopping a turn: the turn's message listener now detaches when the
stream settles, so those messages run as the next turn.
([#4176](https://github.com/triggerdotdev/trigger.dev/pull/4176))
- Add an `onEvent` callback to `TriggerChatTransport` /
`useTriggerChatTransport` that emits typed lifecycle events for sends,
stream connects, first chunk, and turn completion. Send-success metrics,
time-to-first-token, and "sent but never answered" watchdogs become a
few lines of client code.
([#4187](https://github.com/triggerdotdev/trigger.dev/pull/4187))

  ```ts
  onEvent: (event) => {
if (event.type === "message-sent") metrics.timing("chat.send_ms",
event.durationMs);
if (event.type === "first-chunk") metrics.timing("chat.ttft_ms",
event.sinceSendMs ?? 0);
  },
  ```

- Large batch payloads now offload to object storage instead of riding
inline in the trigger request. `batchTrigger` and `batchTriggerAndWait`
(and the by-id and by-task variants) offload any per-item payload over
128KB before sending, the same way single `trigger` and `triggerAndWait`
already do, so a big batch no longer blows past the API body limit.
([#4165](https://github.com/triggerdotdev/trigger.dev/pull/4165))
- Updated dependencies:
  - `@trigger.dev/core@4.5.2`
## @trigger.dev/plugins@4.5.2

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.2`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-09 12:02:34 +00:00
Chris Arderne 34b1a181c2 fix: security release 2026-07-06 (#4199) 2026-07-09 11:58:33 +00:00
nicktrn a6bd370e42 chore: remove end-of-life v3 execution components (#4194)
v3 (engine V1) is end-of-lifed and the v3 clusters are gone, so this
removes the dead v3 execution code from the monorepo. It's the first
pass of TRI-11824 - the webapp v3 code paths are deliberately left
untouched and gated for a follow-up.

## Apps

Deletes the three v3-only execution apps and their build wiring:

- `apps/coordinator`, `apps/kubernetes-provider`, `apps/docker-provider`
- `.github/workflows/publish-worker.yml` - it built only those three;
the v4 worker publish is a separate workflow
- Their references in `.changeset/config.json`, `.cursorignore`,
`CHANGESETS.md`, `CONTRIBUTING.md`, `.server-changes/README.md`
- `pnpm-lock.yaml` regenerated to prune the apps and their app-only
dependencies (`socket.io`, `@kubernetes/client-node`, `p-queue`,
`execa`, `prom-client`, `tinyexec`)

## Core

Removes the helpers in `@trigger.dev/core` that only those apps used -
`ProviderShell`, `SimpleLogger`, the `Exec`/process helpers,
`isExecaChildProcess`, `getTextBody`, and `testDockerCheckpoint`. Each
was verified to have no remaining consumers anywhere in the repo.

Kept the helpers still used elsewhere: `ExponentialBackoff` (warm-start
client), `HttpReply`/`getJsonBody` (serverOnly http server),
`SimpleStructuredLogger` (widely used), and
`ZodNamespace`/`ZodSocketConnection` (still referenced by legacy v3
webapp code, hence the follow-up pass).

The `./v3/apps` and `./v3/serverOnly` export subpaths remain - only dead
members were trimmed from their barrels, so no `package.json` exports
changed.

## Verification

`@trigger.dev/core` builds, and `typecheck` passes for core, supervisor,
cli-v3, run-engine, redis-worker, and webapp.

refs TRI-11824
2026-07-08 19:47:20 +02:00
Eric Allam fbd86b6ee9 feat(sdk): onEvent observability callback on the chat transport (#4187)
## Summary

`sendMessage` from `useChat` gives no feedback about whether a message
actually reached the backend, and the `fetch` override is wire-level: it
requires knowing endpoint semantics, cannot attribute requests to
messages, and misses the headStart first-turn POST entirely. This adds a
typed `onEvent` observability callback to `TriggerChatTransport` /
`useTriggerChatTransport` so send-success metrics, time-to-first-token,
and "sent but never answered" watchdogs become a few lines of client
code.

## Example

```ts
const transport = useTriggerChatTransport({
  task: "my-chat",
  accessToken: ({ chatId }) => mintChatAccessToken(chatId),
  onEvent: (event) => {
    switch (event.type) {
      case "message-sent":
        // Durably acknowledged by the session's input stream, not just "request accepted".
        metrics.increment("chat.message_sent", { source: event.source });
        metrics.timing("chat.send_duration_ms", event.durationMs);
        break;
      case "message-send-failed":
        metrics.increment("chat.message_send_failed", { status: event.status });
        break;
      case "first-chunk":
        metrics.timing("chat.ttft_ms", event.sinceSendMs ?? 0);
        break;
      case "turn-completed":
        metrics.timing("chat.turn_duration_ms", event.sinceSendMs ?? 0);
        break;
    }
  },
});
```

## Design

One callback, one discriminated union (`ChatTransportEvent`):

- `message-sent` / `message-send-failed`: terminal send outcomes with
`messageId`, a `source` discriminator (submit, regenerate, steer,
action, stop, head-start), `durationMs`, `bodyBytes`, the append's
idempotency key (`partId`, also stored on the server-side record), and
error + HTTP status on failure. `message-sent` means the append was
durably acknowledged, after any internal token-refresh retries.
- `stream-connected` (with a `resumed` flag and the cursor it connected
from), `first-chunk` (chunk type plus `sinceSendMs` for
time-to-first-token), `turn-completed` (`sinceSendMs` full-turn latency
and the agent's committed input cursor), and `stream-error` follow the
response side, so a send can be paired with the answer that should
follow it. `messageId` on response events is client-side attribution
from the last turn-producing send on that chat.

Emissions sit at the transport's existing choke points, covering every
send path uniformly (including steering and headStart, which the fetch
override cannot observe). Exceptions thrown by the callback are
swallowed: observability can never break the chat. The React hook keeps
the callback live across renders instead of freezing the first-render
closure.

## Verification

Unit tests drive the transport directly with the `fetch` override as the
network stub (send success/failure per source, stream lifecycle, resumed
flag, field enrichment, callback exceptions swallowed). Verified
end-to-end against a realistic metrics setup in the ai-chat reference
app (counters, send-duration and TTFT histograms, and both watchdogs
built purely on these events): a healthy two-turn chat produces exactly
the expected event sequence and TTFT values; an oversized append records
`message_send_failed` with status 413; and killing the worker after a
durable send fires both `sent_but_no_stream` and `sent_but_unanswered`,
reproducing and detecting the "message disappeared" failure mode that
motivated this feature.
2026-07-08 11:01:40 +01:00
Wes Mason 76c37ecd24 feat(sdk,core,webapp): offload large batch payloads to object storage (#4165)
## Summary

`batchTrigger` and `batchTriggerAndWait` (and the by-id and by-task
variants) now offload any per-item payload over 128KB to object storage
before sending, the same way single `trigger`/`triggerAndWait` already
do since
[#3785](https://github.com/triggerdotdev/trigger.dev/pull/3785). A batch
of large items no longer inflates the request body past the API limit.

## Demo

A live local run: `batchTriggerAndWait` of 5 items × 300KB (1.5MB
total). Each item offloads to object storage, so the receiver run rows
hold a 65-byte `application/store` pointer instead of the 300KB body,
and every item round-trips (received == sent).

<img width="1000" height="494" alt="batch large-payload offload demo"
src="https://github.com/user-attachments/assets/77ae3958-97d6-4b5c-ab25-39b217caefbc"
/>


## Design

Both the array and streaming batch paths funnel through
`executeBatchTwoPhase`, so offloading happens once there: each item is
measured, then offloaded through the existing
`conditionallyExportPacket` when it crosses 128KB, with bounded
concurrency so a big batch doesn't fire an unbounded number of presigned
PUTs.

Because items are offloaded before the request, SDK batches arrive as
small `application/store` references, so the server-side inline offload
during item ingest (parallelised in
[#3777](https://github.com/triggerdotdev/trigger.dev/pull/3777)) mostly
no longer fires for them.

Every trigger and item also carries its pre-offload serialised size as
`options.payloadSize`. The trigger span records that value, so an
offloaded payload shows its real size instead of the size of the small
object-store reference (previously the span measured the reference).
2026-07-07 16:21:48 +00:00
Chris Arderne aa74e68c71 feat(sdk): add bulk replay to api and sdk (#4105)
## Summary

Adds SDK and API support for run bulk actions. You can now create bulk
cancel or replay actions from `@trigger.dev/sdk` using run IDs or the
same filters as `runs.list()`, then retrieve, list, poll, or abort the
action by its `bulk_` handle.

Tests, docs, changesets added.

## Design

The dashboard bulk action service now accepts structured filters instead
of reading directly from a dashboard request, so the dashboard and API
share the same creation path. Replay actions created through the API are
attributed with the existing `api` trigger source, while
dashboard-created actions keep `dashboard`.

The SDK exposes the new surface under `runs.bulk.*`, including
`targetRegion` for replay region overrides and cursor pagination for
listing bulk actions.

## Filters and runIds

Nuance on filters. If `filter` is provided, it MUST have at least one
key. This is to remove the footgun of passing no filter and selecting
all runs.

```typescript
   { action: "cancel", runIds: ["run_1"] } // valid
   { action: "cancel", runIds: [] } // invalid, min(1)
   { action: "cancel", filter: { status: "FAILED" } } // valid
   { action: "cancel", filter: {} } // invalid
   { action: "cancel", filter: {}, runIds: ["run_1"] } // invalid
```
2026-07-07 15:43:30 +01:00
Eric Allam add0a7da0a fix(sdk,core): stop chat sessions dropping messages that arrive during a turn (#4176)
## Summary

Sending a message to a chat whose run had ended could make the message
vanish: the continuation run replayed already-answered messages, never
processed the new one, and a page refresh lost it entirely. Chasing that
report surfaced four composing message-loss bugs in the chat session
runtime; this PR fixes all of them, each with a regression test.

## The fixes

1. **Stale resume cursor.** Records delivered while a run was suspended
(the waitpoint path) advanced the SSE resume counter but not the
committed-consume cursor, so the `session-in-event-id` header stamped on
turn-completes went stale by one record per suspended turn. Continuation
boots seed from that header, which is what made them replay
already-processed messages. `session.in.wait()` now advances both
cursors.

2. **Only the first buffered message dispatched.** Messages arriving
during a turn are consumed into a buffer whose end-of-turn pickup
dispatched only the first entry; the buffer was recreated each turn, so
the rest were discarded, and since consuming a record commits the cursor
the loss was permanent. A continuation boot's replay delivers several
records back-to-back, which put the user's new message at index 1 or
later. The buffer now outlives the turn and drains one message per turn
in both `chat.agent` and `chat.createSession` (whose equivalent buffer
was never read at all).

3. **Post-stop window in `chat.createSession`.** The turn's message
listener stayed attached through the stopped turn's post-stream work, so
a message sent shortly after stopping a turn was consumed into the dead
steering queue and lost. The listener now detaches when the stream
settles, matching the `chat.agent` loop.

4. **Handler leak on errored turns.** A turn that threw outside the
streaming section (for example from an `onTurnStart` hook) leaked its
message listener. Previously that silently lost mid-turn messages; with
the loop-level buffer it would have duplicated them instead. The
subscription handle is now detached by the turn's catch/finally, and
`chat.createSession` defensively detaches its prior turn's listener when
user code exits a turn without `complete()`/`done()`.

## Verification

Reproduced end-to-end with the ai-chat reference project before the fix
(message consumed but never answered, two replayed turns, gone on
refresh) and verified after (single clean turn, survives refresh,
turn-complete cursors strictly advancing). Regression tests in
`packages/trigger-sdk/test/pending-message-drain.test.ts` cover all
four, each verified red against the unfixed behavior. A smoke sweep of
the standard chat scenarios (basic send, multi-turn, suspend/resume,
mid-stream refresh, stop, steering, cancel + continue, and the
`createSession` variant) passes on the final branch state.
2026-07-07 14:10:46 +01:00
Matt Aitken 1a033b665b fix(webapp,core): retry run resume through transient database outages (#4161)
## Summary

When the platform database is briefly unreachable while a run is
resuming from a wait, the run no longer fails with
`TASK_EXECUTION_ABORTED`. The worker now retries the resume through the
outage instead of aborting on the first blip.

## Root cause

Resuming a run calls the engine's `continue` worker-action endpoint.
That route caught every error and returned a `422`, which the worker's
HTTP client treats as non-retryable. So a transient Prisma
infrastructure error (for example `P1001` "Can't reach database server")
was flattened into a permanent failure: the worker gave up, force-killed
the run process, and completed it with `TASK_EXECUTION_ABORTED`.

## Fix

- The `continue` route now lets infrastructure errors propagate to the
generic 500 handler (message scrubbed, and retryable by the worker's
HTTP client), the same treatment the trigger path already gives them via
`isInfrastructureError`. Genuine validation errors (snapshot mismatch,
invalid state) still return `422`, so a stale retry stays non-retryable.
Resuming is idempotent server-side (guarded by the snapshot id), so
retrying is safe.
- The worker's `continueRunExecution` calls (both the
runner-to-supervisor and supervisor-to-engine hops) retry with a longer,
jittered backoff so they can ride out an outage lasting tens of seconds,
and the jitter keeps a fleet of resuming runs from stampeding the
database the moment it recovers.

Builds on #3960, which scrubbed the leaked message on these routes but
left the status non-retryable.

No changeset: this is a server-side behaviour fix recorded via
`.server-changes`. The `@trigger.dev/core` edits are internal run-engine
worker plumbing, not a public API change.
2026-07-07 11:51:59 +01:00
github-actions[bot] c584236937 chore: release v4.5.1 (#4126)
🚀 Publish Trigger.dev Docker / units (push) Failing after 22s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 22s
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
## Summary
1 improvement.

## Improvements
- Extend the SSO plugin contract with WorkOS Directory Sync (SCIM)
support.
([#4148](https://github.com/triggerdotdev/trigger.dev/pull/4148))

<details>
<summary>Raw changeset output</summary>

# Releases
## @trigger.dev/build@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.1`
## trigger.dev@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/build@4.5.1`
  - `@trigger.dev/core@4.5.1`
  - `@trigger.dev/schema-to-json@4.5.1`
## @trigger.dev/python@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/build@4.5.1`
  - `@trigger.dev/core@4.5.1`
  - `@trigger.dev/sdk@4.5.1`
## @trigger.dev/react-hooks@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.1`
## @trigger.dev/redis-worker@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.1`
## @trigger.dev/rsc@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.1`
## @trigger.dev/schema-to-json@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.1`
## @trigger.dev/sdk@4.5.1

### Patch Changes

- Updated dependencies:
  - `@trigger.dev/core@4.5.1`
## @trigger.dev/core@4.5.1


## @trigger.dev/plugins@4.5.1

### Patch Changes

- Extend the SSO plugin contract with WorkOS Directory Sync (SCIM)
support.
([#4148](https://github.com/triggerdotdev/trigger.dev/pull/4148))
- Updated dependencies:
  - `@trigger.dev/core@4.5.1`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-06 19:17:36 +01:00
Oskar Otwinowski de65370fb9 feat(webapp): Directory Sync (SCIM) for Identity & Access (#4148)
Extend the SSO plugin contract for directory sync and apply membership
effects
from the accounts webhook worker: provision users in mapped groups (role
from
group mapping, else the org default role), deprovision on removal, and
keep a
sticky-removal tombstone so JIT never silently re-adds a removed user.
JIT and
Directory Sync coexist; roles default to Developer (the JIT default-role
picker
has no 'None'). Changing a group's role in the dashboard re-applies it
to that
group's current members immediately. The Directory Sync settings section
(group→role mapping, external-domain + manual-membership policy,
deferred Save)
appears once a domain is verified — independent of SSO — gated by the
hasSso
flag. The settings page polls the whole page while entitled with
override-aware
drafts so in-progress edits are never clobbered.
2026-07-06 09:31:14 +02:00
Daniel Sutton 092b9ef07a fix(run-ops): DNS-safe, sortable base32hex run id (replace base62 KSUID) (#4154)
## Problem

The run-ops split mints NEW-store run ids as **27-char base62 KSUIDs**.
The supervisor writes the run id into the Kubernetes pod name
(`runner-<id>`), and pod names must be DNS-1123 labels (lowercase
`[a-z0-9-]`) — so uppercase base62 ids make k8s reject the pod (422) and
**those runs never launch** (they loop in `PENDING_EXECUTING` until the
heartbeat-stall handler nacks them, forever). `.toLowerCase()` can't fix
it: base62 has both `A`(10) and `a`(36) as distinct symbols, so folding
collides distinct ids and destroys sort order.

## Fix: change the encoding, not the structure

Mint a **26-char lowercase base32hex** run id:

```
run_<24-char base32hex core><region char><version char>
      [ 6-byte ms timestamp ][ 9 CSPRNG bytes ]
```

- **base32hex** (RFC 4648 §7, alphabet `0-9a-v`): lowercase,
order-preserving, DNS-safe; 15 bytes → exactly 24 chars, no padding.
Hand-rolled encode/decode (no new dependency).
- **48-bit ms timestamp** in the leading bytes → plain string sort ==
creation order at millisecond resolution.
- **72 bits CSPRNG** entropy; PK unique constraint is the backstop (no
retry loop).
- **region / version** are raw positional chars (read via one `charAt`
before decoding/routing), version = `"1"`.

DNS-safe from birth and hyphen-free, so **firekeeper is unchanged** —
`runner-<id>-attempt-N` → strip `runner-`, cut at first hyphen still
recovers the exact id incl. region+version.

## Residency discriminator: length → version char

`classifyKind`/`classifyResidency` (`runOpsResidency.ts`) previously
distinguished NEW vs LEGACY by **id length**. That gets ambiguous with a
third format. It now discriminates on the **version char at a fixed
position** (`isRunOpsIdBody`: 26 chars, `[25] === "1"`, base32hex
alphabet) → NEW; everything else → LEGACY. Total, never throws. The
`Residency` (NEW/LEGACY) contract the routing store consumes is
unchanged; the `"ksuid"` `ResidencyKind` label is retained only because
it's the persisted `runOpsMintKsuid` feature-flag value.

## Scope / verification

- Generator + discriminator in `@trigger.dev/core` isomorphic; mint path
+ all id-shape call sites swept (~40 webapp files); changeset added
(`@trigger.dev/core` patch).
- Core unit tests (encode/decode round-trip + property, generator shape,
ms sort-order incl. intra-second, parse partitioned-vs-legacy,
firekeeper round-trip): **24 pass**. `@trigger.dev/core` builds; webapp
typechecks; format/lint clean.

## Open decisions (flagged, not silently chosen)

1. **Backward-compat**: existing 27-char base62 KSUID runs now classify
LEGACY. On test cloud these are the broken/looping runs that never
completed, so this is acceptable — but worth a conscious call before
prod. No transitional length-recognition added (keeps the discriminator
clean).
2. **Storage collation**: the sort guarantee is byte-order — if the
run-ops id column is `TEXT` with default locale collation it's silently
not honored. Confirm whether `COLLATE "C"` / `BYTEA` is needed on the
run-ops schema.
3. **Region sourcing** wiring — see `regionCharForRegion` /
`REGION_CODES`.


---

## ⚠️ Required migration — deploy in lockstep

This PR renames a persisted feature-flag key/value and an env var. These
are **not** changed by the code alone and must be migrated when this
deploys, or affected orgs silently fall back to `cuid` minting (no crash
— `defaultValue: "cuid"`):

1. **Env var** (terraform): `RUN_OPS_MINT_KSUID_ENABLED` →
`RUN_OPS_MINT_ENABLED` (carry the value over).
2. **DB** `organization.featureFlags`: migrate both the key and value
together:
   - key `runOpsMintKsuid` → `runOpsMintKind`
   - value `"ksuid"` → `"runOpsId"`

Until an org's flag row is migrated, its `runOpsMintKind` lookup misses
and it mints `cuid` (legacy) — so no NEW-store ids for that org until
the data lands.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 10:05:54 +01:00
Daniel Sutton 4cfa596271 feat(core): id-shape residency classifier + ksuid mint primitives (run-ops split base) (#4112)
## What

Foundation for the run-ops database split: an isomorphic **id-shape
residency classifier** and the **ksuid mint primitives**, added to
`@trigger.dev/core` under `v3/isomorphic`.

- **`runOpsResidency.ts`** — classifies a run id by its shape: 25-char
cuid → `LEGACY`, 27-char ksuid → `NEW`. Pure and environment-free (safe
on both client and server).
- **`friendlyId.ts`** — ksuid mint primitives and id helpers.
- Both exported via `v3/isomorphic/index.ts`.

## Why

This is the **base of a stacked series** implementing the run-ops DB
split (routing run-execution data to a dedicated database by id-shape).
Later PRs in the series consume this classifier and these primitives to
route reads and writes across the two databases.

On its own this PR is **purely additive** — new isomorphic helpers with
unit tests, no runtime wiring, and no behaviour change to existing code
paths.

## Tests

Unit tests for the classifier (`runOpsResidency.test.ts`) and the id /
mint primitives (`friendlyId.test.ts`).

## Notes

- Draft, stacked on `main`; subsequent PRs in the series build on top of
this one.
- A changeset for `@trigger.dev/core` will be added before this is
marked ready for review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 11:05:39 +01:00
Oskar Otwinowski c7f6ed501c feat(cli,core): add opt-in dev-only telnet log streaming (#4110)
Stream dev logs over a local telnet/TCP socket. `trigger dev` mirrors
its terminal output on port 6767 by default (override with
--telnet-logs-port or TRIGGER_DEV_TELNET_LOGS_PORT, 0 disables). webapp,
supervisor, and coordinator each expose an opt-in stream gated on a
per-service *_TELNET_LOGS_PORT env var. New
@trigger.dev/core/v3/telnetLogServer module (localhost-only,
backpressure-safe, plain-text) plus optional static Logger.onLog /
SimpleStructuredLogger.onLog sinks.

Then you (or your agent) can use `nc` to connect and filter out the
stream.
<img width="1103" height="239" alt="image"
src="https://github.com/user-attachments/assets/b4d47efc-8a57-4185-a159-10f2806627ae"
/>
2026-07-02 19:16:14 +00:00
Chris Arderne 448443a024 chore: bump internal node to 22 and standardise (#4084)
Bumps the internal/toolchain Node version to the latest 22.x LTS
(`22.23.1`) and standardises it across the repo. Scope is the **platform
toolchain + the repo's own runtime images** (all `20 → 22` *upgrades*,
off the now-EOL node 20).

### Main changes
- Node `20.20.2 → 22.23.1` across all CI workflows, `.nvmrc`,
`CONTRIBUTING.md`, and the OSS `docker/Dockerfile` (digest-pinned).
- `@types/node → 22.20.0` (root dep + pnpm `overrides`, so the whole
workspace resolves to it); lockfile regenerated.
- `sdk-compat` matrix: adds Node 24 + 26 (keeps 20, still in `engines`).
- **App runtime images → node 22** (were on EOL node 20):
`apps/coordinator` → `node:22.23.1-bookworm-slim`;
`apps/docker-provider` + `apps/kubernetes-provider` → `node:22-alpine`
(reusing the exact digest `apps/supervisor` already runs, so all four
worker images are now identical). Stage aliases renamed off `node-20`.

### Possible issues / test notes
- `@types/node` 22.x can surface new TS errors — typecheck (now on 22)
is the gate.
- **Smoke-test the v3 worker path** — `coordinator` (`crictl`/CRI calls)
and the docker/kubernetes providers (talking to their daemons) now run
on node 22 (alpine/musl for the providers). Upgrade off EOL so low-risk,
but it's deployed runtime code with its own `publish-worker.yml`
pipeline.
2026-07-02 18:13:36 +01:00
Chris Arderne c7861be520 chore: activate no-unused-vars and import linters (#4096)
Once this is merged, oxlint is at a pretty sensible baseline.

**Enable `no-unused-vars`, `typescript/consistent-type-imports`, and
`import/no-duplicates` lint rules**

Turns on three previously-disabled oxlint rules across the monorepo and
fixes all violations:

- **`no-unused-vars`** – enabled as an error with standard ignore
patterns: unused function arguments are ignored by default (`args:
"none"`), variables/caught errors/destructured array elements prefixed
with `_` are allowed, and rest siblings are permitted.
- **`typescript/consistent-type-imports`** – enforced as an error; all
type-only imports now use the `import type` syntax.
- **`import/no-duplicates`** – enforced as an error; duplicate import
statements from the same module have been merged.

The remaining commits clean up the violations found across the codebase:
removing unused variables/imports/type aliases, adding `_` prefixes to
intentionally unused bindings, fixing duplicate imports, and converting
value imports to `import type` where appropriate.
2026-07-02 11:37:05 +01:00
github-actions[bot] 86ef3c4979 chore: release v4.5.0 (#3998)
📚 Publish docs / publish (push) Has been cancelled
🚀 Publish Trigger.dev Docker / units (push) Failing after 20s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 20s
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
# Trigger.dev v4.5.0

4.5.0 is the GA of the AI Agents platform. Everything built during the
prerelease line (durable agents, Sessions, AI Prompts) is now stable on
the `latest` tag, alongside a set of SDK and runtime improvements.

## AI Agents (`chat.agent`)

Run Vercel AI SDK chat completions as durable Trigger.dev tasks instead
of fragile API routes. A conversation runs as one long-lived task keyed
on `chatId`, so it survives page refreshes, network blips, redeploys,
and crashes, and every turn is a span in the dashboard.

```ts
import { chat } from "@trigger.dev/sdk/ai";
import { streamText, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export const myChat = chat.agent({
  id: "my-chat",
  run: async ({ messages, signal }) => {
    return streamText({
      ...chat.toStreamTextOptions(), // system prompt, compaction, steering, telemetry
      model: anthropic("claude-sonnet-4-5"),
      messages,
      abortSignal: signal,
      stopWhen: stepCountIs(15),
    });
  },
});
```

## Sessions

The durable primitive underneath `chat.agent`, usable on its own: a
run-aware, bidirectional stream channel keyed on a stable `externalId`
whose `.in` / `.out` streams survive run boundaries (suspend, crash,
idle-timeout, redeploy). One Session spans many runs, which makes it a
good fit for agent inboxes and approval flows.

```ts
import { sessions } from "@trigger.dev/sdk";

// Create the session and trigger its first run (idempotent on externalId)
await sessions.start({
  type: "inbox",
  externalId: userId,
  taskIdentifier: "inbox-agent",
});

const session = sessions.open(userId);
await session.in.send({ text: "hello" });

const stream = await session.out.read({ signal: AbortSignal.timeout(30_000) });
for await (const chunk of stream) console.log(chunk); // durable across run swaps
```

## AI Prompts

Define prompt templates as code, versioned on every deploy, and override
the text or model from the dashboard without redeploying
(environment-scoped). Each generation links back to its prompt version
for usage, cost, and latency.

```ts
import { prompts } from "@trigger.dev/sdk";
import { z } from "zod";

export const supportPrompt = prompts.define({
  id: "customer-support",
  model: "gpt-4o",
  variables: z.object({ customerName: z.string(), issue: z.string() }),
  content: `You are a support agent for Acme.
Customer: {{customerName}}
Issue: {{issue}}`,
});

// Honors any active dashboard override, else the current deployed version
const resolved = await supportPrompt.resolve({ customerName: "Alice", issue: "Can't log in" });
// resolved.text, resolved.model, resolved.version
```

## `useChat` integration

`useTriggerChatTransport` is a Vercel AI SDK `ChatTransport` that runs
`useChat` over Trigger.dev realtime with no API routes. Text, tool
calls, reasoning, and `data-*` parts stream natively, and it works with
AI SDK v5, v6, and now v7.

## First-turn fast path (`chat.headStart`)

Runs the first turn in your warm server process while the agent boots in
parallel, cutting cold-start time-to-first-chunk roughly in half
(measured ~2.8s to ~1.2s). Available via the new
`@trigger.dev/sdk/chat-server` subpath.

## Human-in-the-loop, stop, and steering

The agent control surface: tool approvals (`needsApproval` +
`addToolApprovalResponse`), client-driven stop-generation, mid-execution
steering (`pendingMessages`), and between-turn context injection
(`chat.inject` / `chat.defer`), all durable across the conversation.

## Agent Skills

`skills.define({ id, path })` bundles a `SKILL.md` folder into your
deploy image. The agent gets a one-line summary up front and loads the
full instructions plus scoped `bash` / `readFile` tools on demand
(progressive disclosure), so a capability is something the model reaches
for rather than a pre-declared typed tool.

## `trigger skills` for coding assistants

`trigger skills` installs version-pinned Trigger.dev skills plus a
bundled docs snapshot into Claude Code, Cursor, GitHub Copilot, and
Codex, so your assistant's Trigger.dev knowledge stays current with your
installed SDK version. `trigger init` now offers to set up the MCP
server and skills too.

## Model library

A new Models page in the dashboard: a catalog of models grouped by
provider with context window, capabilities, and input / output pricing
per 1M tokens, plus a "Your models" tab showing per-model usage, cost,
and cache-hit sparklines from your actual traffic.

## Dev branches

Run multiple local `trigger dev` sessions in parallel (separate git
worktrees or coding agents) without runs colliding, each isolated with
its own dashboard, via `trigger dev --branch <name>`.

## `TriggerClient`

An instantiable client so one process can trigger and read across
projects, environments, and preview branches, each with its own auth and
baseURL, with no shared global state.

```ts
import { TriggerClient } from "@trigger.dev/sdk";

const prod = new TriggerClient({ accessToken: process.env.TRIGGER_PROD_KEY });
const preview = new TriggerClient({
  accessToken: process.env.TRIGGER_PREVIEW_KEY,
  previewBranch: "signup-flow",
});

await prod.tasks.trigger("send-email", { to: "user@example.com" });
await preview.runs.list({ status: ["COMPLETED"] });
```

## SDK and runtime

- AI SDK 7 support (v5 and v6 still supported), with OpenTelemetry
telemetry auto-wired
- Large trigger-payload offload: trigger payloads at or above 128KB
upload to object storage automatically, using the same auth and baseURL
as the trigger call
- Region support on the runs API: filter runs by region and read each
run's executing region (also on MCP `list_runs`)
- Duplicate task-id detection: `dev` and `deploy` fail with a clear
error instead of silently overwriting
- `envvars.upload` gains an `isSecret` flag to import redacted secret
variables
- Retry hardening: `TASK_MIDDLEWARE_ERROR` now retries under the task's
retry policy

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-02 11:26:52 +01:00
Chris Arderne 7851f5cad3 chore: fix flakey build worker test (#4090)
## Summary

Fixes flaky CLI v3 E2E tests by removing fixture-level parallelism.

The suite was using `describe.concurrent`, but each fixture test mutates
its fixture workspace during setup: removing `node_modules`,
renaming/restoring lockfiles, and running package installs. On
Windows/npm this can race or hit file-lock/cache contention, causing
intermittent failures.

## Fix

Run the CLI v3 E2E fixtures serially instead of concurrently.

## Expected impact

This should make the Windows/npm E2E job more stable. The E2E step may
slow down from ~30s to roughly ~60–90s in typical runs, with a
conservative upper bound around ~2 minutes.
2026-07-01 12:18:29 +01:00
Matt Aitken 5df9fb6463 fix(core,cli): retain idempotency key metadata beyond 1000 keys per run (#4094)
Fixes #4046

## Problem

When a single run creates more than 1000 idempotency keys (e.g. a large
batch trigger where each item calls `idempotencyKeys.create()`), the
original key and scope metadata is silently dropped for all but the most
recent 1000 keys.

`idempotencyKeys.create()` returns a plain 64-char hash and stores the
`{ key, scope }` mapping in an in-process catalog keyed by that hash.
That catalog was a fixed-size **LRU capped at 1000 entries**. Once a run
creates more than 1000 keys, the earliest mappings are evicted, so when
the SDK later looks them up to attach `idempotencyKeyOptions` to the
trigger call, it finds nothing and sends `undefined`. The affected runs
then:

- report `ctx.run.idempotencyKey` as the raw hash instead of the
user-provided key
- have no `idempotencyKeyScope`
- show empty `idempotency_key` / `idempotency_key_scope` in the
dashboard and analytics

Deduplication still works (the hash is intact); only the human-readable
metadata is lost, which makes the failure silent and hard to notice.

## Fix

- Replace the LRU catalog with an unbounded in-memory catalog, so every
key created within a run keeps its metadata regardless of how many are
created.
- Clear the catalog at each run boundary via
`resetExecutionEnvironment()` (both dev and managed workers), matching
how every other per-run manager is reset. Deployed workers reuse one
process across many runs (warm starts), so this bounds memory to a
single run's keys instead of accumulating across runs — which is the
reason the size cap existed in the first place.

## Tests

- New public-API test creates 3000 keys and asserts all of them
(including the first) retain their key/scope — this fails on `main` and
passes with the fix.
- New test for the in-memory catalog covers store/retrieve/overwrite,
large-N retention (no eviction), and `clear()`.
- New test asserts the catalog is emptied after a run-boundary reset.
- Replaces the previous LRU catalog + its eviction tests.

Verified: `@trigger.dev/core` and `trigger.dev` both build; all
idempotency tests pass. Changeset added (patch).
2026-07-01 12:15:07 +01:00
Chris Arderne bfa902bd18 chore: enable more linters (#4080)
Re-enables ~15 oxlint rules that were blanket-disabled before.
2026-07-01 08:43:12 +01:00
nicktrn baaecfcff3 chore(core): redact sensitive flag values from exec command logs (#4087)
The `Exec` helper in `@trigger.dev/core` logs command args at debug
level (and in its output/error metadata). For commands that take a
credential directly on the command line - `--password`, `--token`,
`--secret`, etc. - that value is logged verbatim, so turning on debug
logging can surface secrets in log sinks.

This masks the value of known credential-bearing flags (both `--flag
value` and `--flag=value` forms) before the args are logged. The
executed command is untouched - only the logged copy is redacted. Added
a small unit test for the redaction helper.
2026-06-30 17:59:06 +00:00