Update trigger - #4
Open
tylerc-govsignals wants to merge 1726 commits into
Open
tylerc-govsignals wants to merge 1726 commits into
tylerc-govsignals wants to merge 1726 commits into
Conversation
## Summary Add explicit types to native dashboard buttons and enforce `react/button-has-type`. This prevents action buttons from accidentally submitting a surrounding form. Shared button primitives retain their caller-selected submit and reset semantics with documented lint exceptions. Base: [#4691](#4691)
## Summary Finish associating dashboard form labels with their controls and enforce `jsx-a11y/label-has-associated-control`. Repeated data store dialogs use unique generated IDs, story controls and notification filters have explicit associations, and display-only status text no longer uses label elements. Base: [#4694](#4694)
## Summary Use native label and checkbox behavior for `CheckboxWithLabel` and enforce `jsx-a11y/no-noninteractive-element-interactions`. The component no longer simulates checkbox activation with click handlers on non-interactive wrappers. Native change events now drive the controlled checked state. Base: [#4696](#4696)
## Summary Replace mouse-only dashboard actions with native buttons. Copy, remove, and stop-generation controls now expose keyboard focus and accessible names. Hover-revealed actions remain mounted so keyboard users can discover them, and a decorative clipboard icon no longer captures clicks. Base: [#4697](#4697)
## Summary Enable keyboard-event and static-element interaction safeguards across the dashboard. Earlier stack changes move actionable behavior to native controls. This final enforcement keeps narrowly documented exceptions for focus forwarding, scoped Escape handling, CodeMirror focus, and pointer-driven table column resizing. `jsx-a11y/no-autofocus` remains disabled. Base: [#4701](#4701)
…al_deployment_id (#4661) Migrations only, no code reads them yet. Postgres: nullable non-unique externalId on WorkerDeployment plus a CONCURRENTLY-built (environmentId, externalId) index in its own migration file. ClickHouse: external_deployment_id String DEFAULT '' on task_runs_v2 (plain String, not LowCardinality - commit SHAs are high-cardinality). Part of task run version skew protection (TRI-12998).
An external deployment id is an opaque, caller-chosen name for a release - a commit SHA, a CI run id, a release tag. This adds the shared contract that both halves of the feature read, and nothing else: no deploy writes one yet and no trigger sends one. ExternalDeploymentId is defined once and reused by InitializeDeploymentRequestBody.externalId and TriggerTaskRequestBody.options.externalDeploymentId, so a value accepted by one half can never be rejected by the other. A value that is blank once trimmed is treated as absent rather than rejected, so an unset CI variable expanding to an empty string is not a 400. The 128 character limit fits a SHA-256 commit hash with room for composite ids, and EXTERNAL_DEPLOYMENT_ID_MAX_LENGTH is the single source of truth that the request schemas and the CLI both read. RunAnnotations.externalDeploymentId records the request, not the outcome: lockedToVersionId and taskVersion are overwritten when a run locks, whereas this stays true forever, and it can carry the pin for a run parked before its deployment exists. Also lands the runtime discovery helpers as pure functions over an environment reader: the explicit TRIGGER_EXTERNAL_DEPLOYMENT_ID variable, the platform and CI commit-SHA table, and the TRIGGER_AUTOMATIC_SKEW_VERSION_PROTECTION gate. Nothing calls them yet. refs TRI-13000
A deploy can carry an opaque external id (commit SHA, CI run id, release tag). Repeating an id that already deployed returns the existing version as a no-op instead of rebuilding; an id with a build in flight is rejected with 409 naming that version; a failed id rebuilds freely. --force is non-destructive to deployments that already succeeded - both persist and the higher version wins - but cancels a build still in flight, so one id never has two live builds racing to define it. Cancelling writes a terminal status and appends a finalized event, which aborts a build the platform drives; a build it does not drive keeps running but can never land, and the CLI says so. Ids are deliberately not unique - reuse is resolved in application code by highest version, never timestamps. The no-op path mints no build credentials and no event stream (TRI-12923). What that means for callers: a --force rebuild leaves two deployments holding one id, and runs triggered with it go to the higher version once the rebuild lands, so the takeover needs no separate promotion. Until a successful build exists for an id, runs triggered with it park and then expire rather than falling back to current - a failed build is therefore visible to the caller as expired runs, not as runs on the wrong release.
…4664) The SDK discovers an external deployment id at runtime (explicit TRIGGER_EXTERNAL_DEPLOYMENT_ID always; platform commit-SHA variables and generic fallbacks when TRIGGER_AUTOMATIC_SKEW_VERSION_PROTECTION=1) and sends it alongside lockToVersion; the server resolves precedence (version > external id > current). An id held by a deployed deployment pins the run to that worker; an in-flight or unknown id parks the run in PENDING_VERSION with the id in TaskRun.annotations, wakes it pinned when a deployment carrying the id finalizes (ClickHouse candidates, Postgres authoritative), and expires it after a deadline that re-checks Postgres before acting. Parking outranks delaying and preserves delayUntil. The id is projected to ClickHouse task_runs_v2.external_deployment_id during replication. Redis cache for id-to-worker resolution, guarded version-aware writes. Ids are not unique. Several deployments can hold one id - a --force rebuild is the ordinary way to get there - so resolution always picks the highest version among the candidates, never the newest by timestamp. The rule is applied identically on both paths that can bind a run to a worker: resolveExternalDeployment at trigger time, and PendingVersionSystem when a landing deployment wakes a parked run. Version comparison is numeric on the counter half, so 20260807.10 outranks 20260807.9. A run whose id never lands expires at the deadline with EXTERNAL_DEPLOYMENT_NOT_FOUND and an error naming the id it waited for, which is what a failed build or a typo looks like from the caller. Default deadline is one hour (EXTERNAL_DEPLOYMENT_PARK_DEADLINE_MS). Debounce registration happens in both the parked and the delayed branch through one helper, so a debounced run that parks still binds its debounce key; without it every later trigger for the same key created another parked run, and all of them executed when the deployment landed. The two DELAYED-only status checks in DebounceSystem also accept PENDING_VERSION, without which the lock-contention fallback would rethrow a 5xx the SDK retries and amplifies, and the fast path would push every trigger on a parked key through the redlock. Resolution is skipped in development. A dev environment cannot hold a WorkerDeployment - trigger dev registers a BackgroundWorker with nothing behind it, and deploy --env refuses dev - so an external deployment id there could only ever park, and the parked run then expired against the dev TTL while a connected dev worker sat idle. The id is still annotated so the dashboard shows what the app sent (TRI-13000).
…#4665) Deployments page: an always-visible External ID column after Deployed by, and an External ID row in the deployment inspector under Worker type, both showing an en dash when a deploy carried no id. The Vercel Linked column now renders before Git, still only when a Vercel integration is connected. Also corrects the blank-row colSpan, which was already off by one before this column existed. Run inspector: an External deployment ID row between Version and SDK version, read from the run annotations, so an operator can see which id a run was pinned to - including a run that expired before its deployment ever arrived, where the locked version is empty but the id is the whole story. Buffered runs read the id from the same annotations rather than reporting none. Long ids are head-truncated with the full value behind the copy button: a commit SHA is meaningful in its prefix, and the inspector panel can be narrowed to 250px, where an unbroken 40-character SHA would otherwise scroll the properties list sideways and push the copy button off-panel (TRI-12923, TRI-13000).
…ed parked runs (#4708) Two defects that surface when a run parked on an external deployment id gets pushed by a debounce key. Both were reproduced against a local instance before being fixed. ## 1. The run is expired before it is due ``` now | status | statusReason | delayUntil | expiredAt 13:57:06 | EXPIRED | EXTERNAL_DEPLOYMENT_NOT_FOUND | 14:01:37 | 13:57:02 ``` Killed 4m35s before its own scheduled start, blaming a missing deployment. **Why.** The park deadline is armed **once**, when the run is first parked, from `max(now, delayUntil) + deadline`. Debounce pushes `delayUntil` out afterwards and nothing re-arms it: - `rescheduleDelayedRun` reschedules `enqueueDelayedRun:<id>`, not `expireParkedExternalDeploymentRun:<id>` - the redis-worker reschedule is an update-only `ZADD … XX`, and a parked run has no `enqueueDelayedRun` job, so that call is a silent no-op Repeat triggers on one key walk `delayUntil` away from a deadline that no longer moves. Once it crosses, the run dies while parked and not yet due. **Fix.** The expiry job already loads `delayUntil`, so it re-arms from the current value and returns instead of expiring a run that is not due. The guard lives in the expiry job rather than the debounce path deliberately: it covers **every** caller that moves `delayUntil`, so a future call site can't reintroduce this by forgetting to re-arm. It stays bounded by the debounce max-duration contract, so a hot key can't postpone expiry indefinitely. ## 2. The run reports itself as delayed while it is parked ``` RUN_CREATED | PENDING_VERSION | Run is waiting for a deployment of 'debounce-test-2' DELAYED | DELAYED | Delayed run was rescheduled to a future date ← after one debounce push ``` The row stays `PENDING_VERSION`; the latest snapshot claims `DELAYED`, so the run page describes a parked run as delayed. Happens on the *first* push. **Fix.** `rescheduleRun` hardcoded `DELAYED`/`DELAYED`. The snapshot statuses are now supplied by the caller and **default to `DELAYED`**, so the ordinary delayed path is byte-identical, and `rescheduleDelayedRun` passes the parked statuses through when the run is parked. ## Reproducing Repeated triggers on one debounce key against an id that hasn't landed: ```bash curl … -d '{"options":{"externalDeploymentId":"x","debounce":{"key":"k","delay":"5m"}}}' ``` Three triggers correctly fold into one parked run; the defects show up on the pushes. ## Testing Two tests, each verified red before green and failing alone: - a run whose delay was pushed past the deadline stays `PENDING_VERSION` instead of expiring - a debounce push on a parked run leaves a `RUN_CREATED`/`PENDING_VERSION` snapshot, not `DELAYED` `56 passed` across parking, pendingVersion, delayedRunSystem and debounce; `43 passed` in `PostgresRunStore`. Typecheck, lint, format clean. ## Notes - Stacks on #4665, so it lands after the whole external-deployment-id series. - No changeset: this fixes unreleased behaviour introduced by the stack below it, so no user has seen it. - Both found by Devin's review on #4664, and both confirmed end to end on a local instance before fixing.
…, add a ref input (#4710) ## Problem Every merge to main touching the agent queued a gated `staging`+`prod` deploy that sat `pending` on a reviewer approval nobody grants routinely. Because the gated runs never completed, they never drained the concurrency queue and cancelled each other, so the Actions tab filled with never-completing runs and the agent only ever actually deployed via a manual dispatch + approval. The reviewer gate bought nothing here: the agent deploys with `--skip-promotion`, so a deploy lands **dormant** and nothing goes live until the consuming webapp flips `DASHBOARD_AGENT_VERSION`. Promotion is already a deliberate act (the env-var flip); gating the dormant deploy on top of that just created the pile-up. ## Change - **Remove the reviewer gate** by dropping the required-reviewers rule on the `dashboard-agent-*` environments (repo-settings change, done). The `environment:` key **stays** so the per-environment scoped deploy token still resolves — no secret migration. - **`workflow_dispatch` `ref` input** — deploy a specific commit SHA, branch, or tag; defaults to the ref the run launches from. Checkout uses `github.event.inputs.ref || github.sha`. - **Require the ref to be an ancestor of `main`.** Constrains which commit gets deployed to merged code only. A push is always main's tip (passes trivially); a dispatched unmerged ref is rejected before the deploy step. Because an explicit `ref:` checkout doesn't create remote-tracking branches, `origin/main` is fetched explicitly before `git merge-base --is-ancestor`. - **`cancel-in-progress: false`** (kept). Cancelling the runner wouldn't stop the remote build (it finishes server-side), and a superseding concurrent deploy would race the same project's indexer. With the gate gone, deploys are short, so a brief queue can't pile up. - `max-parallel: 1` stays (parallel deploys of the same project race at the indexer). ## Owner actions (repo settings — not in the diff) 1. **Remove required-reviewers** on `dashboard-agent-staging` and `dashboard-agent-prod` — done. 2. **Add a deployment branch policy** on both environments restricting deployments to `main`. This is the authoritative token guard: `workflow_dispatch` runs the workflow file from the selected ref, so the in-file ancestor check alone can't protect `TRIGGER_ACCESS_TOKEN` (a branch could edit the check out). GitHub enforces the branch policy server-side against `GITHUB_REF` regardless of file contents. With it in place, the workflow only runs (and the token is only exposed) when dispatched from `main`, and the in-file check then constrains the independent `ref` input to merged commits. ## Pile-up root cause The stacking was caused by the **reviewer gate** (runs waited forever, so the queue never drained), not by `cancel-in-progress`. Removing the gate is what fixes it; `cancel-in-progress` stays `false`.
…nch (#4724) <!-- ccr-slack-attribution --> _Requested by **Iss** · [Slack thread](https://triggerdotdev.slack.com/archives/C045W9WM3E1/p1787161814493949)_ **Before:** archiving a branch dropped the query string on the way back to the branches list, so the list reset to page 1. Working down a long list meant re-navigating to the page you were on after every archive. **After:** you land back on the exact page you archived from, with `page`, `search` and `showArchived` intact. The archive action now redirects to the page the request came from instead of rebuilding a bare branches path. ## How The archive dialog already submits the page it was opened from as a hidden `redirectPath` field (`${location.pathname}${location.search}`), and the failure path already redirected to it — only the success path ignored it and rebuilt the path with `branchesPath`/`branchesDevPath`, which have no query string. Both paths now redirect to the submitted path, run through the existing `sanitizeRedirectPath` helper to keep the redirect same-origin (the same idiom used by `resources.batches.$batchId.check-completion`). ## ✅ Checklist - [x] I have followed every step in the [contributing guide](https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md) - [x] The PR title follows the convention. - [x] I ran and tested the code works --- ## Testing Three files change: - `apps/webapp/app/routes/resources.branches.archive.tsx` — the fix. - `apps/webapp/test/archiveBranchRedirect.test.ts` — new test that drives the archive action and asserts the redirect `Location`: the query string survives on both success and failure, and an off-origin `redirectPath` falls back to `/`. Reverting the fix makes two of the three cases fail, so the test covers the regression. - `.server-changes/archive-branch-keeps-list-page.md` — release-note entry, since this is a user-facing server-only change. Also ran `pnpm run typecheck` and `oxlint` for `apps/webapp` — both clean. --- ## Changelog Archiving a branch now returns you to the same page of the branches list instead of resetting it to page 1. --- ## Screenshots _None — no visual change._ --------- Co-authored-by: Claude <noreply@anthropic.com>
## Summary Speeds up webapp test jobs by balancing measured work across runners, reducing repeated container setup, and ensuring test workers release shutdown resources promptly. Unit tests run across 24 duration-aware shards, while E2E tests run across two balanced shards. ## Design `RunEngine` shutdown now closes processing resources before support resources, continues cleanup if one close fails, and reuses one shutdown promise for concurrent callers. Redis workers clear completed shutdown deadlines so finished tests no longer wait on idle timers. Container-heavy suites are split only where it improves parallelism, and repeated replication and engine fixtures are consolidated where one end-to-end case provides coverage. Timing weights are refreshed for all affected files. Dependency installation overlaps container pulls, and both workflows use WarpBuild's Node setup action.
…#4734) ## Summary `goose up` against `internal-packages/clickhouse/schema` panics on `main` today, so ClickHouse migrations cannot be applied from a fresh checkout. Renumbering the external deployment id migration from 040 to 041 clears it. ## Root cause Two migrations claim version 40. [#4615](#4615) added `040_create_task_events_search_v2.sql`, and [#4661](#4661) added `040_add_task_runs_v2_external_deployment_id.sql` a day later. #4661 was opened before #4615 merged, so 040 was genuinely free at branch time, and because the two files have different names there is no textual conflict for git or a rebase to surface. Both merged green, and no workflow in this repo runs `goose`, so the collision only shows up the first time someone actually migrates. goose parses the numeric filename prefix as the version and refuses duplicates: ``` panic: goose: duplicate version 40 detected: .../040_create_task_events_search_v2.sql .../040_add_task_runs_v2_external_deployment_id.sql ``` It aborts while collecting the directory, before executing any SQL, so nothing was half applied and there is no migration state to repair. This migration gets renumbered rather than the `task_events_search_v2` one because goose keys on the version number and not the filename: version 40 is already recorded wherever 040 has been applied, so renaming that file would re-run an applied migration. Verified with a full `goose up` against ClickHouse 26.2.19.43 (the image pinned in `internal-packages/testcontainers`): migrations apply cleanly through version 41, and `task_runs_v2.external_deployment_id` lands as `String DEFAULT ''`.
## Summary Enables exhaustive React Hook dependency checking and resolves the existing violations across the dashboard and React hooks package. Effects and callbacks now track current values without introducing request, subscription, or render loops. ## Design Dependencies are included directly when the hook lifecycle should follow them. Timers, Remix fetchers, and realtime subscriptions use stable callbacks or latest-value refs where restarting work would change behavior. Unnecessary memoization was removed where ordinary derivation is clearer. Full lint and typechecks for the webapp and React hooks package pass.
## Summary Adds targeted lint suppressions for components built around libraries that React Compiler intentionally declines to memoize, plus one unsupported function-reference pattern. Each suppression is scoped to the affected component so other compiler diagnostics remain actionable.
## Summary Calls dashboard hooks directly instead of passing them as ordinary callback values, and subscribes to optional Ariakit stores through an unconditional hook. This keeps hook ordering stable while preserving the existing behavior when a provider is absent.
## Summary Keeps render inputs and shared regular expressions immutable. Grouped selects now compute each section's shortcut offset directly from preceding sections, which also makes numeric shortcuts follow the displayed item order reliably.
## Summary Uses explicit bucket timestamps when rendering usage charts instead of anchoring missing timestamps to the current render time. Tooltips now remain stable across rerenders, and examples use a deterministic timestamp.
## Summary Derives session and API key expiry states from a timestamp captured by each route loader. Every status on a page now uses one consistent point in time instead of changing according to when an individual component rerenders.
## Summary Records when live metric responses arrive and uses that timestamp to evaluate gauge freshness and waiting duration. Cached or failed responses remain untrusted until revalidated, while rendered values stay stable between polling updates.
## Summary Captures chat-history age when the menu opens so rerenders cannot change labels mid-view. The waitpoint deadline form also reuses one intentional wall-clock snapshot for all calculations in a render.
## Summary Makes stable dashboard history refs explicit memo inputs and scopes the remaining compiler diagnostics to callbacks whose local handlers or lifetime-stable values cannot be represented accurately in dependency arrays.
Reloading a chat while the agent is still answering now shows the
message being answered. The incoming message was previously persisted
only once the turn finished, so a refresh mid-answer rendered the reply
with no question above it.
The runtime now writes the message at the start of the turn, carrying
the previous turn's stream cursors so a mid-answer reload still resumes
from the last completed turn. That write is not awaited before the model
runs. The output stream is held on it instead, so no part of the answer
reaches the frontend before the message is durable, and time to first
token is unchanged.
The same ordering is available to your own writes as
`chat.deferBeforeOutput()`:
```ts
onTurnStart: async ({ chatId, uiMessages }) => {
chat.deferBeforeOutput(
db.chat.update({ where: { id: chatId }, data: { messages: uiMessages } })
);
},
```
It runs alongside the model like `chat.defer()`, but the answer waits
for it, so the next page load always sees the write. It orders the write
against what the frontend can see and not against the model, so a write
that a tool reads back during the same turn still needs to be awaited.
Mono-RevId: a84c08af51b376f5a49091d001ed1ec59149881f
… reads secret-key only
Session public tokens can now be narrowed to one stream with
`read:sessions:{id}:out`, and reading a session's `.in` channel now
requires a secret key. The dashboard agent's browser token uses the
narrowed scope.
Mono-RevId: 1214e6dd60256c7f1323e891580c16d500c8fd9a
## Docs: intro rework, agent anatomy rewrite, and more AI agent examples - **Introduction.** Reworked the docs landing page to lead with AI agents and workflows: a cleaner hero, a core concepts section, and short sections for building agents, scaling and scheduling, and self hosting. Corrected the licensing wording and refreshed the card styling. - **Anatomy of an agent.** Rewrote the page so it teaches the three parts of a chat agent (the agent task, the durable session, and the frontend transport) and traces a single message through them, instead of only linking out to other pages. - **AI agent examples.** Added more example projects to the AI agents overview: an ElevenLabs voice agent, the ask Trigger chat agent, a batch LLM evaluator, and a Claude thinking chatbot. Mono-RevId: fb6db42f103bdc6d35af62163ee33427fb67211d
… serves it Pull the MinIO server and client images from Quay instead of Docker Hub, which no longer serves them. This affects the CI image pre-pull, the testcontainers default for `MinIOContainer`, and the local `docker-compose.yml` services. Both images stay pinned to the release and multiplatform digest that were already in use, so nothing about the images themselves changes. Mono-RevId: 90fecfc34d8b72beee6f7728ac88f78096604dc4
… appending after it `chat.agent`: after a Head Start turn whose handed-over tool call was followed by more tool steps, the next turn no longer fails with `tool_use ids must be unique`. The completed response reuses the warm step's message id, and the runtime swaps the partial's run in the model context for the response's. The synthesized partial converts to zero model messages because its tool call has no output yet, so the swap matched an empty slice and spliced the response in behind the still-present partial. An empty old run now counts as no match, and the runtime rebuilds the model context from the merged history instead. Includes a changeset for `@trigger.dev/sdk` (patch). Mono-RevId: 1a0b38b58f96139ede347ec307437721dfc6c226
…ed turn `chat.agent` now keeps a `chat.history` edit made inside `onTurnComplete` after a failed turn. Previously the edit was applied only when the turn succeeded, so a failure record or a card the hook closed on the error path never reached the transcript. Details of the error-path behaviour: - The edit is converted to model messages before any state is replaced, so a conversion that throws (for example from a tool's `toModelOutput`) leaves the history exactly as it was. - The stream's partial answer stays marked non-final only while the message under its id is still that partial by content. A hook that clones the history keeps it partial; a hook that finishes it in place saves it as final. - A history edit left pending by an earlier hook that threw is discarded before the failed turn continues, whether or not the agent defines `onTurnComplete`, so it can never be mistaken for a later hook's edit or leak into the next turn. Includes a changeset for `@trigger.dev/sdk` (patch). Mono-RevId: ac849a203949f8a719a8c33866004cf517d5f795
Removes the transitional attributes input column from the task events table now that writers serialize attributes themselves. Mono-RevId: eea5932cc8936c5a12f20160ad44d94f4808de1b
…t transcript storage The in-dashboard agent now persists its conversation through the `chat.agent` transcript storage adapter over its own message rows, instead of the platform snapshot plus its own hook writes. The runtime writes the question at turn start, the answer at turn complete, every history edit an action makes, and the resume cursors; the panel reads the same rows. - The hook writes the runtime now owns are removed. Investigation settlement keeps its own transaction so a settled row and its closing card still commit together. - The storage adapter normalises message bodies before writing and refuses to save into a chat owned by another tenant. - The panel's resume cursor comes from the transcript cursors, falling back to the previous session column for chats written by the earlier agent build. - Tests assert on the transcript the runtime saved instead of on store call counts. - Watch wakes and consented investigations are answered with `chat.turn()`. The action files what it has to say as a user-role request under a stable id and returns the turn, so an ordinary turn answers it with the agent's prompt, tools, hooks and transcript save; the response is pinned to the id the panel already knows the record by. The request messages are hidden by the panel and excluded from the message cap. A wake whose wording is fixed is still streamed directly with no model call. Mono-RevId: 7e72e1783e44bd7e7fdec3add6e3c331622c24d1
…ublish chore(docker): rebuild the apt upgrade layers on every webapp image publish The webapp Dockerfile upgrades OS packages with `apt-get upgrade` in three stages, but those layers had no changing input, so the build cache reused them for months and the published image kept known-fixed CVEs. A new `APT_REFRESH` build arg, set by the publish workflow to the build timestamp, is referenced ahead of each upgrade so the layers rebuild on every publish. The layers below them (dependency install, the webapp build) still cache as before. Also pins the node base image to its multi-platform index digest instead of the architecture-oriented one. Same image content, no behaviour change. Mono-RevId: 156d96fdb686a73b5a8af56668344a64fdf7e7a6
Mono-RevId: 6620c9d8e5ba543eeb8ef18cc82e944e3b3fb365
Mono-RevId: 81e6ee9f8902eea13a49f3e491005b7e22abf7f5
## Summary
5 new features, 37 improvements, 12 bug fixes.
## Breaking changes
- Reading a session's `.in` channel (`GET /realtime/v1/sessions/{id}/in`
and `/in/records`) now requires a secret key. Public tokens, including
`read:sessions:{id}`, get a 403; they can still read `.out` and append
to `.in`.
## Highlights
- A failed write to a realtime or chat session stream no longer crashes
the process running it, and a dropped chat session output write is now
logged instead of swallowed.
([`fb25c0149`](https://github.com/triggerdotdev/trigger.dev/commit/fb25c0149c6c734f942f6f41210b197ed4b1f736))
## Improvements
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
- The `playwright` build extension now works with Playwright 1.58 and
later. 1.58 changed the `playwright install --dry-run` output, which
made deploy image builds fail while downloading the browsers.
([#4881](https://github.com/triggerdotdev/trigger.dev/pull/4881))
- Rename the dev error link to "Ask Trigger about this error"
([`f999516a0`](https://github.com/triggerdotdev/trigger.dev/commit/f999516a0d8ae2f3a19c76e11aae935e60c81d2c))
- Automatically archive up to three inactive development branches when
creating a branch at the plan limit. Connected and recently active
branches remain protected, and the CLI reports which branches were
archived.
([`dd55fdb5b`](https://github.com/triggerdotdev/trigger.dev/commit/dd55fdb5b821b7cb51d15cd102481c87659985ef))
- When the build log stream cannot be opened or disconnects during a
build server deploy, the CLI now explains that the deployment itself is
unaffected and exits immediately with a non-zero code, since it can no
longer confirm the outcome. Previously a disconnect printed the raw
stream error and left the process hanging.
([#4887](https://github.com/triggerdotdev/trigger.dev/pull/4887))
- Build logs no longer include docker's registry login output, most
notably the credential-storage warning on failed builds.
([#4909](https://github.com/triggerdotdev/trigger.dev/pull/4909))
- Reduce sensitive values in CLI and SDK diagnostics, secure files
created by `trigger env pull`, and remove credentials from collected Git
remote metadata.
([`ff05824c1`](https://github.com/triggerdotdev/trigger.dev/commit/ff05824c1bdf1c2276d202ed84328c948cc290a3))
- `chat.agent` persists a conversation through a `TranscriptStorage`: an
adapter with `load` and `save` that the runtime drives after every turn,
failed turn and history-changing action. The platform snapshot stays the
default; bring your own to write the conversation to your database as it
happens. Each save carries both the changes since the last one (so a row
store writes only what changed, and an undo is one `truncateAfter`) and
the whole transcript as it now stands (so a document store writes it
as-is with no state of its own).
([#4896](https://github.com/triggerdotdev/trigger.dev/pull/4896))
```ts
chat.agent({
id: "my-chat",
storage: myTranscriptStorage,
run: async ({ messages, signal, streamText }) =>
streamText({ model, messages, abortSignal: signal }),
});
```
`chat.createLoadTranscriptAction(storage)` and `useLoadTranscript` read
the conversation back the same way for every storage, and
`runTranscriptStorageTests` from `@trigger.dev/sdk/ai/test` checks an
implementation against the contract.
Compaction summaries and `chat.inject` context now survive a
continuation run, and crash recovery runs for every agent, including one
that owns its own context. `hydrateMessages` is deprecated in favour of
`loadContext` on a storage. The snapshot format is now version 2, which
older SDK versions cannot read.
- `chat.agent`: a run that recovers a session with more than one
in-flight user message no longer drops the unanswered ones if it
restarts mid-recovery. Recovered messages now hold the resume cursor
until each has been answered, so a restart re-answers the rest instead
of resuming past them. Previously the cursor could advance past messages
that were only held in memory, so a crash before they were dispatched
lost them.
([#4907](https://github.com/triggerdotdev/trigger.dev/pull/4907))
- End a chat conversation from inside the agent with `chat.close({
reason })`. The session row is closed, further sends are refused with
HTTP 409, and the run exits without scheduling a continuation, so a
budget cap, a completed goal, or a signed-out user can stop the
conversation rather than only the current run.
([`0a23814a0`](https://github.com/triggerdotdev/trigger.dev/commit/0a23814a0896205da227520bd417bd490a017379))
```ts
chat.agent({
id: "budgeted-agent",
run: async ({ messages, signal }) =>
streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }),
onBeforeTurnComplete: async ({ chatId }) => {
if (await overBudget(chatId)) {
chat.close({ reason: "Monthly budget reached" });
}
},
});
```
The current turn still streams in full. Decide the close before the turn
ends (`run()`, `prepareStep`, `onBeforeTurnComplete`) so the closed
state rides out on that turn's final record and the user sees it as soon
as the answer finishes. `TriggerChatTransport` picks the close up from
the response stream or from a refused send, exposes it as
`transport.sessionStatus(chatId)` plus
`transport.sessionClosedReason(chatId)`, and stops sending and
reconnecting. Closing a session from outside with `sessions.close()` now
also reaches a live run, so an idle or suspended agent exits on its next
wake instead of waiting out its idle timeout. Writes to a closed
session's named side channels are refused with the same 409.
- Reading a page of a chat agent's conversation no longer downloads the
whole conversation. The saved transcript now carries an index, so asking
for the most recent messages fetches only those messages, and history
loads in roughly constant time however long the chat gets.
([`b7e86f2af`](https://github.com/triggerdotdev/trigger.dev/commit/b7e86f2afe1b1b4e38f1f2f00222eb172e2d0ee3))
A paged read also returns only the conversation itself. The model-side
context an agent keeps, its compacted history and any injected context,
is no longer included, so it cannot reach a browser through a
load-transcript server action.
The built-in storage is deliberately basic about long conversations:
once an agent has compacted, it keeps roughly the last hundred messages
and drops the rest, so what it rewrites each turn stops growing. A
conversation that never compacts is kept whole. If your app renders
history further back than that, give the agent your own transcript
storage.
The saved format has changed and an older SDK cannot read it, so a
deployment rolled back to an earlier version will not find a readable
transcript for conversations the newer version already saved, and those
conversations continue from the live stream tail instead. Roll forward
rather than back, or keep your own transcript storage.
- Adds the `GetDeploymentArtifactUrlResponseBody` schema for the
deployment artifact download URL endpoint.
([`1a5ad1e5f`](https://github.com/triggerdotdev/trigger.dev/commit/1a5ad1e5fbc54efbc1c61077ed966477e94a9849))
- Deployments now return the `--external-id` they were deployed under as
`externalId`, and a run can read its own from
`ctx.deployment.externalId`. Also fixes the deployments list failing
when one deployment had no git metadata.
([`879e8975b`](https://github.com/triggerdotdev/trigger.dev/commit/879e8975b13605fd7607c87bd906e640fca90755))
- Add an optional `appliedSchedulePolicy` field to the schedule API
response. It is present only when a non-overridable plan policy applies
a minimum window to a schedule (e.g. a free-plan schedule's minimum run
interval); the configured `window` continues to be returned separately
and unchanged.
([`2991bb48a`](https://github.com/triggerdotdev/trigger.dev/commit/2991bb48a284f8b0c140b7a3890f1e4ee73e224d))
- Triggering a task whose id cannot be represented in a URL (for example
an id containing an unpaired surrogate) now fails with a clear error
naming the task id, instead of a cryptic URI error.
([`ad821eaea`](https://github.com/triggerdotdev/trigger.dev/commit/ad821eaead317bfe60e6d4ca10c00b6fdcbbc5fd))
- Actions can now become turns. `onAction` edits history with
`chat.history`; to answer after the edit, return `chat.turn()` and a
turn runs on the edited history with everything a turn has: the agent's
system prompt and tools, steering, compaction, injected instructions,
`onTurnStart` and `onTurnComplete`, and persistence. A regenerate is
`chat.history.slice(0, -1); return chat.turn();`.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
```ts
onAction: async ({ action }) => {
if (action.type === "regenerate") {
chat.history.slice(0, -1);
return chat.turn();
}
if (action.type === "undo") chat.history.slice(0, -2); // edit only
},
```
Returning a `StreamTextResult`, `string` or `UIMessage` from `onAction`
is no longer supported and now fails with an error pointing to
`chat.turn()`. A response produced that way skipped every turn
guarantee, and its delivery to the browser was unreliable: the frontend
never read the stream `transport.sendAction` returned, so a regenerate
that appeared to work on the server did not render.
History edits made by an action are still persisted as before:
platform-managed snapshots are written after the edit, and apps with
their own store mirror the edit themselves.
- `run()` now receives a `streamText` with your agent's managed options
already applied, so they cannot be lost by leaving out the spread:
([#4884](https://github.com/triggerdotdev/trigger.dev/pull/4884))
```ts
run: async ({ messages, signal, streamText }) =>
streamText({ model, messages, abortSignal: signal });
```
Spreading `chat.toStreamTextOptions()` still works and is equivalent.
The difference is what happens when your options collide with the
managed ones. Passing `tools` after the spread replaces the skill tools,
and passing your own `prepareStep` replaces the managed one, which
silently switches off steering, compaction and injected context. The
managed `streamText` merges tools and composes `prepareStep` instead, so
neither can be turned off by accident.
`system` can be set at the call site, on `chat.agent({ system })`, or
through `chat.prompt.set()`, but only in one of them: setting it in two
places throws, because no single shape merges two system values across
every supported AI SDK version, and dropping one silently is the failure
this seam exists to prevent. Injected instructions append to whichever
one is in play.
`chat.agent()` also takes `registry`, `cacheControl` and
`systemProviderOptions` now, so a managed prompt's model and its cache
breakpoint no longer have to be passed at the call site.
`chat.toStreamTextOptions()` applies them as well, so spreading it into
the `streamText` imported from `ai` stays equivalent to the one `run()`
receives.
`chat.headStart` and `chat.startHeadStart` hand their `run` the same
thing, carrying the options the handover protocol depends on. There it
matters more: re-setting `messages`, `prompt`, `stopWhen` or
`abortSignal` after a spread breaks the handover rather than degrading a
feature, and nothing caught it. On the managed one those four keys are a
type error; `tools` is yours to pass.
- Actions are sent through `useChat` so a turn that follows one renders
like any turn. `TriggerChatTransport` recognises `body.action` on a
`useChat` request and sends it as an action, so `sendMessage(undefined,
{ body: { action } })` or `regenerate({ body: { action } })` sends the
action and `useChat` owns the response: it streams into the message
list, `status` and `error` behave as for a message, and `stop` works.
`useChatActions({ sendMessage })` in `@trigger.dev/sdk/chat/react` is a
two-line convenience over that.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
```tsx
const { sendMessage } = useChat({ id: chatId, transport });
const { sendAction } = useChatActions({ sendMessage });
sendAction({ type: "regenerate" });
```
Previously the frontend docs said `useChat` consumed the stream
`transport.sendAction` returns; it never did, so an action's answer was
never rendered by an app following them. `transport.sendAction` still
returns a stream that callers outside `useChat` must read, and now
accepts `{ abortSignal, metadata }`, with per-action metadata merged
over the transport's `clientData`.
- Chat sessions can now be pinned to a deployment, so a conversation
keeps talking to the agent version its release shipped with, and follows
the pin on its own when your app redeploys. Opt out with `triggerConfig:
{ externalDeploymentId: null }` or `versionSkew: "hold"`. Also fixes
`AgentChat` ignoring `maxDuration`, `region` and `lockToVersion`, and a
restored `AgentChat` session never picking up a new deployment id.
([`1133ad45e`](https://github.com/triggerdotdev/trigger.dev/commit/1133ad45e7c1edbc2ff053678e37146c155521ef))
- `chat.agent`: a `chat.history` edit made in `onTurnComplete` after a
failed turn is now kept. Previously the edit was applied only when the
turn succeeded, so a failure record or a card the hook closed on the
error path never reached the transcript.
([`3f67c71c0`](https://github.com/triggerdotdev/trigger.dev/commit/3f67c71c0c68ea53ba1859b4e31054cc5d52293a))
- `chat.agent`: after a Head Start turn whose handed-over tool call was
followed by more tool steps, the next turn no longer fails with
`tool_use ids must be unique`. The runtime kept the warm step's pending
tool call in the model context alongside the completed response that
already contained it.
([`8b72e6c06`](https://github.com/triggerdotdev/trigger.dev/commit/8b72e6c0616b1570d57f35b5e2a736791ad3c6f0))
- `chat.agent`: a continuation boot no longer re-dispatches the message
that resumed it, and a turn with no new user message no longer calls the
model. Previously a resumed run could answer the same message twice, and
the second attempt failed against providers that reject a trailing
assistant message, overwriting an answer that had already completed.
([`35e57e785`](https://github.com/triggerdotdev/trigger.dev/commit/35e57e785c6c80ee22329c597287ff88a3486e4e))
- `useTriggerChatTransport` now picks up changes to `accessToken`,
`startSession` and `fetch` on re-render, so a chat that stays mounted
while the surrounding page changes no longer keeps sending to the
endpoint captured on first render.
([`9ae9c1ae4`](https://github.com/triggerdotdev/trigger.dev/commit/9ae9c1ae43a91e21afcc6e2c97c4b63b9b0bff71))
- Steering messages are now kept in the conversation when you drive
turns yourself with `chat.createSession()` or `chat.MessageAccumulator`.
Previously a message that arrived mid-answer shaped that answer and then
existed nowhere: it was missing from `turn.uiMessages`, so an app
persisting from there never stored it, missing from `turn.messages`, so
every later turn answered as though it had never been sent, and it was
not queued as its own turn either. It now lands in both, the same way it
does on `chat.agent`.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
- Injected system context is merged into a single instruction block, so
it works on every supported AI SDK version. Note that a cached system
prompt gives up its cache entry for as long as an injection is live,
since the cached prefix has changed.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
- `chat.inject()` with `role: "system"` now works. It previously put the
system message into the conversation, which AI SDK 7 rejects for every
provider: the next turn died with a generic "An error occurred." and
persisted an empty assistant message, so the agent looked like it had
stopped answering. System-role context is now appended to the model's
instructions, which is also the only way to inject context the agent
treats as trusted.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
Two things to know. Instructions are delivered by
`chat.toStreamTextOptions()`, so a `run()` that calls `streamText`
without spreading it does not receive a system-role injection. The
conversational lane has no such requirement. And an injection applies to
the next turn only, rather than repeating on every turn that follows it.
Every inference call in that turn sees it, so a `run()` that builds
options more than once gets the same instructions each time. An
instruction injected after an action has run, and before the next
message, reaches that next turn rather than the one after it.
- Undo, edit and regenerate now survive a run ending. History rolled
back from `onAction` was only kept in the running worker's memory, so
the rollback held while that worker stayed warm and then reverted on the
next continuation. The undone messages came back, minutes later, with no
error. This also holds when the turn before the action failed: the
rollback used to be written against the cursor from before that turn, so
a continuation could replay output the failed turn had already
superseded.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
- Server-side `AgentChat` streams now reconnect when the connection
drops mid-turn instead of ending with a truncated reply, and a turn that
still cannot be resumed ends with an error rather than a silent
truncation.
([`8bf27a629`](https://github.com/triggerdotdev/trigger.dev/commit/8bf27a62937b5858f4963d65a9d7802982f1724e))
- Session public tokens can now be narrowed to one stream: `read: {
sessions: "chat_123:out" }` grants read access to that session's `.out`
channel only, without access to the session record or its other
channels.
([`33cf5701b`](https://github.com/triggerdotdev/trigger.dev/commit/33cf5701b4536012d45e365761c4a36067ea5f1d))
- Steering messages injected mid-answer are now part of the
conversation, both for your hooks and for the model on later turns.
Previously they reached the model for the answer they steered and
reached the browser, but nothing else: `onTurnComplete` never saw them,
so an app storing its own transcript lost the instruction the answer was
shaped by, and it vanished from the conversation on reload. The model
also forgot the instruction from the next turn onwards, answering as
though the message had never been sent, while the chat UI still showed
it. This holds when the steered turn fails part-way, and when
`pendingMessages.prepare` reshapes the message: later turns now see the
same form the steered turn did, not the original message.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
Approving a tool call no longer undoes compaction. A tool-approval
continuation used to rebuild the model's context from the full
conversation, so a chat that had been summarised to fit the context
window was sent the whole transcript again on the next call, and could
go over the limit it had just been compacted to avoid.
If you worked around this by saving steering messages as they arrive, in
`pendingMessages.onReceived` for example, that write now duplicates the
one you get from `newUIMessages`. Drop it, or skip messages you have
already stored.
- Reloading a chat while the agent is still answering now shows the
message being answered. Previously the incoming message was only
persisted once the turn finished, so a refresh mid-answer showed the
reply arriving with no question above it.
([`986811008`](https://github.com/triggerdotdev/trigger.dev/commit/9868110089cb08817801bc4e1026dd6c781be1be))
Adds `chat.deferBeforeOutput()` for app-owned writes that the next page
load has to see. Like `chat.defer()` the work is not awaited by the hook
that registers it, so it runs alongside the model and costs no time to
first token, but the answer is held until it lands. Use it for the
conversation or message write you previously had to `await` in
`onTurnStart`, as long as nothing else in the turn reads that write
back: it orders the write against what the frontend can see, not against
the model, so a tool that reads the same row still needs an awaited
write.
- chat.agent transcript fixes: a turn that errors before the model
produces any content no longer stores an empty assistant message, an
error thrown without a message now shows a generic error instead of a
blank one, and a custom transcript storage no longer needs to preserve
exact message JSON for a compaction to survive a continuation.
([#4910](https://github.com/triggerdotdev/trigger.dev/pull/4910))
## Bug fixes
- Fixes storage of large trigger payloads for task ids containing a
slash, which could fail the trigger with an "Invalid packet storage
path" error. It affected ids that started or ended with a slash,
contained two slashes in a row, or contained a `.` or `..` path
component. The storage path is now built from a generated id rather than
from the task id, so no task id can produce an unusable one, and
payloads that are already stored are still read from where they were
written.
([`ed37e19c9`](https://github.com/triggerdotdev/trigger.dev/commit/ed37e19c9f70495ba2a067245f8a4a83aaace2c3))
## Server changes
These changes affect the self-hosted Docker image and Trigger.dev Cloud:
- Projects that need a Node.js runtime update can now be handed to a
coding agent: the organization Projects settings page has a button that
copies a ready-to-paste prompt listing every project to update.
- Additional API keys are now enabled by default. New environments no
longer display root API keys, and existing environments can permanently
disable their visibility
- New schedules use a default CRON spread window when none is set,
distributing runs after their scheduled time instead of starting them
all at once. Set an explicit window to override the default.
- Schedules now support a configurable minimum spread window that
applies even when a smaller window is requested.
- The "Cancel in-progress runs when this limit is reached" option on the
billing limit form is now enabled by default when you first configure a
limit, so already-executing runs stop instead of continuing past the
limit. Organizations that have already saved a billing limit keep their
existing choice.
- Creating and archiving Development and Preview branches now requires
the branch management permission, which the Developer role has by
default.
- The assistant in the dashboard no longer has a monthly message limit,
so you can chat with it as much as you like.
Asking it to keep an eye on something and tell you when it happens is
rolling out gradually, so it isn't offered in every organization yet.
- Ask Trigger now opens as a floating window you can drag anywhere and
resize, and the chat header lets you switch it to a right-side panel or
fullscreen. Choose the position it opens in from your account settings.
- The Queues page Allocated tile now explains that it is the sum of your
queue concurrency limits, and no longer shows a warning color when those
add up to more than the environment limit, which is expected.
- Deleting a project now stops its pending runs. Runs that were waiting
on a `delay` or sitting in the queue are cancelled instead of executing
later, and a deleted project no longer sends task failure alerts.
- Fixes an intermittent "Invalid access token" failure caused by the
deployment log stream token expiring while a deploy was still in flight.
- Dashboard pages no longer keep polling for updates while their browser
tab is hidden, which could leave a tab you came back to showing a
connection error instead of your data. Pages refresh when you return to
the tab.
- Stop counting agent LLM calls twice. An agent framework emits a
wrapper span around the inference span that did the work, and both were
priced, so LLM cost aggregates and the AI metrics page reported roughly
double for agent workloads. Per-call figures in the run view were always
correct and are unchanged.
- Retried trigger and batch trigger requests are deduplicated again:
when the SDK automatically retries a request that the server had in fact
already accepted, you get the original run or batch back instead of a
duplicate one.
- Fix the Queues page showing "No activity" on the queue-metrics charts
for some organizations even though their metrics were being collected.
Those charts now display the collected data.
- The queue page's "Oldest wait" card now shows a single clear number
(how long the oldest waiting run has been waiting) with an explanatory
tooltip, and no longer shows a second "worst" figure that could
confusingly read lower than the headline.
- Run replication now recovers on its own after a Redis restart or
outage, in place of logging "Cannot extend an already-expired lock" and
holding the replication slot open until the server is restarted.
Deployments running under a process supervisor can set
`RUN_REPLICATION_MAX_RESUBSCRIBE_ATTEMPTS` to exit and be restarted when
a stream cannot recover.
- Reject waitpoint registrations that target a run outside the
authenticated environment
- Switching environments now keeps you on the current page when a task's
id contains a slash, instead of dropping you back to the list. The test
page for a webhook task whose id contains a slash also opens correctly
now.
- GitHub App installations are now linked only after the installing
GitHub user authorizes the App and is verified to have access to the
installation.
<details>
<summary>Raw changeset output</summary>
# Releases
## @trigger.dev/build@4.6.0
### Minor Changes
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- The `playwright` build extension now works with Playwright 1.58 and
later. 1.58 changed the `playwright install --dry-run` output, which
made deploy image builds fail while downloading the browsers.
([#4881](https://github.com/triggerdotdev/trigger.dev/pull/4881))
- Updated dependencies:
- `@trigger.dev/core@4.6.0`
## trigger.dev@4.6.0
### Minor Changes
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- Rename the dev error link to "Ask Trigger about this error"
([`f999516a0`](https://github.com/triggerdotdev/trigger.dev/commit/f999516a0d8ae2f3a19c76e11aae935e60c81d2c))
- Automatically archive up to three inactive development branches when
creating a branch at the plan limit. Connected and recently active
branches remain protected, and the CLI reports which branches were
archived.
([`dd55fdb5b`](https://github.com/triggerdotdev/trigger.dev/commit/dd55fdb5b821b7cb51d15cd102481c87659985ef))
- When the build log stream cannot be opened or disconnects during a
build server deploy, the CLI now explains that the deployment itself is
unaffected and exits immediately with a non-zero code, since it can no
longer confirm the outcome. Previously a disconnect printed the raw
stream error and left the process hanging.
([#4887](https://github.com/triggerdotdev/trigger.dev/pull/4887))
- Build logs no longer include docker's registry login output, most
notably the credential-storage warning on failed builds.
([#4909](https://github.com/triggerdotdev/trigger.dev/pull/4909))
- Reduce sensitive values in CLI and SDK diagnostics, secure files
created by `trigger env pull`, and remove credentials from collected Git
remote metadata.
([`ff05824c1`](https://github.com/triggerdotdev/trigger.dev/commit/ff05824c1bdf1c2276d202ed84328c948cc290a3))
- Updated dependencies:
- `@trigger.dev/core@4.6.0`
- `@trigger.dev/build@4.6.0`
- `@trigger.dev/schema-to-json@4.6.0`
## @trigger.dev/core@4.6.0
### Minor Changes
- `chat.agent` persists a conversation through a `TranscriptStorage`: an
adapter with `load` and `save` that the runtime drives after every turn,
failed turn and history-changing action. The platform snapshot stays the
default; bring your own to write the conversation to your database as it
happens. Each save carries both the changes since the last one (so a row
store writes only what changed, and an undo is one `truncateAfter`) and
the whole transcript as it now stands (so a document store writes it
as-is with no state of its own).
([#4896](https://github.com/triggerdotdev/trigger.dev/pull/4896))
```ts
chat.agent({
id: "my-chat",
storage: myTranscriptStorage,
run: async ({ messages, signal, streamText }) =>
streamText({ model, messages, abortSignal: signal }),
});
```
`chat.createLoadTranscriptAction(storage)` and `useLoadTranscript` read
the conversation back the same way for every storage, and
`runTranscriptStorageTests` from `@trigger.dev/sdk/ai/test` checks an
implementation against the contract.
Compaction summaries and `chat.inject` context now survive a
continuation run, and crash recovery runs for every agent, including one
that owns its own context. `hydrateMessages` is deprecated in favour of
`loadContext` on a storage. The snapshot format is now version 2, which
older SDK versions cannot read.
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- Automatically archive up to three inactive development branches when
creating a branch at the plan limit. Connected and recently active
branches remain protected, and the CLI reports which branches were
archived.
([`dd55fdb5b`](https://github.com/triggerdotdev/trigger.dev/commit/dd55fdb5b821b7cb51d15cd102481c87659985ef))
- `chat.agent`: a run that recovers a session with more than one
in-flight user message no longer drops the unanswered ones if it
restarts mid-recovery. Recovered messages now hold the resume cursor
until each has been answered, so a restart re-answers the rest instead
of resuming past them. Previously the cursor could advance past messages
that were only held in memory, so a crash before they were dispatched
lost them.
([#4907](https://github.com/triggerdotdev/trigger.dev/pull/4907))
- End a chat conversation from inside the agent with `chat.close({
reason })`. The session row is closed, further sends are refused with
HTTP 409, and the run exits without scheduling a continuation, so a
budget cap, a completed goal, or a signed-out user can stop the
conversation rather than only the current run.
([`0a23814a0`](https://github.com/triggerdotdev/trigger.dev/commit/0a23814a0896205da227520bd417bd490a017379))
```ts
chat.agent({
id: "budgeted-agent",
run: async ({ messages, signal }) =>
streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }),
onBeforeTurnComplete: async ({ chatId }) => {
if (await overBudget(chatId)) {
chat.close({ reason: "Monthly budget reached" });
}
},
});
```
The current turn still streams in full. Decide the close before the turn
ends (`run()`, `prepareStep`, `onBeforeTurnComplete`) so the closed
state rides out on that turn's final record and the user sees it as soon
as the answer finishes. `TriggerChatTransport` picks the close up from
the response stream or from a refused send, exposes it as
`transport.sessionStatus(chatId)` plus
`transport.sessionClosedReason(chatId)`, and stops sending and
reconnecting. Closing a session from outside with `sessions.close()` now
also reaches a live run, so an idle or suspended agent exits on its next
wake instead of waiting out its idle timeout. Writes to a closed
session's named side channels are refused with the same 409.
- Reading a page of a chat agent's conversation no longer downloads the
whole conversation. The saved transcript now carries an index, so asking
for the most recent messages fetches only those messages, and history
loads in roughly constant time however long the chat gets.
([`b7e86f2af`](https://github.com/triggerdotdev/trigger.dev/commit/b7e86f2afe1b1b4e38f1f2f00222eb172e2d0ee3))
A paged read also returns only the conversation itself. The model-side
context an agent keeps, its compacted history and any injected context,
is no longer included, so it cannot reach a browser through a
load-transcript server action.
The built-in storage is deliberately basic about long conversations:
once an agent has compacted, it keeps roughly the last hundred messages
and drops the rest, so what it rewrites each turn stops growing. A
conversation that never compacts is kept whole. If your app renders
history further back than that, give the agent your own transcript
storage.
The saved format has changed and an older SDK cannot read it, so a
deployment rolled back to an earlier version will not find a readable
transcript for conversations the newer version already saved, and those
conversations continue from the live stream tail instead. Roll forward
rather than back, or keep your own transcript storage.
- Adds the `GetDeploymentArtifactUrlResponseBody` schema for the
deployment artifact download URL endpoint.
([`1a5ad1e5f`](https://github.com/triggerdotdev/trigger.dev/commit/1a5ad1e5fbc54efbc1c61077ed966477e94a9849))
- Deployments now return the `--external-id` they were deployed under as
`externalId`, and a run can read its own from
`ctx.deployment.externalId`. Also fixes the deployments list failing
when one deployment had no git metadata.
([`879e8975b`](https://github.com/triggerdotdev/trigger.dev/commit/879e8975b13605fd7607c87bd906e640fca90755))
- Add an optional `appliedSchedulePolicy` field to the schedule API
response. It is present only when a non-overridable plan policy applies
a minimum window to a schedule (e.g. a free-plan schedule's minimum run
interval); the configured `window` continues to be returned separately
and unchanged.
([`2991bb48a`](https://github.com/triggerdotdev/trigger.dev/commit/2991bb48a284f8b0c140b7a3890f1e4ee73e224d))
- A failed write to a realtime or chat session stream no longer crashes
the process running it, and a dropped chat session output write is now
logged instead of swallowed.
([`fb25c0149`](https://github.com/triggerdotdev/trigger.dev/commit/fb25c0149c6c734f942f6f41210b197ed4b1f736))
- Triggering a task whose id cannot be represented in a URL (for example
an id containing an unpaired surrogate) now fails with a clear error
naming the task id, instead of a cryptic URI error.
([`ad821eaea`](https://github.com/triggerdotdev/trigger.dev/commit/ad821eaead317bfe60e6d4ca10c00b6fdcbbc5fd))
## @trigger.dev/react-hooks@4.6.0
### Minor Changes
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- Updated dependencies:
- `@trigger.dev/core@4.6.0`
## @trigger.dev/redis-worker@4.6.0
### Minor Changes
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- Updated dependencies:
- `@trigger.dev/core@4.6.0`
## @trigger.dev/rsc@4.6.0
### Minor Changes
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- Updated dependencies:
- `@trigger.dev/core@4.6.0`
## @trigger.dev/schema-to-json@4.6.0
### Minor Changes
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- Updated dependencies:
- `@trigger.dev/core@4.6.0`
## @trigger.dev/sdk@4.6.0
### Minor Changes
- Actions can now become turns. `onAction` edits history with
`chat.history`; to answer after the edit, return `chat.turn()` and a
turn runs on the edited history with everything a turn has: the agent's
system prompt and tools, steering, compaction, injected instructions,
`onTurnStart` and `onTurnComplete`, and persistence. A regenerate is
`chat.history.slice(0, -1); return chat.turn();`.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
```ts
onAction: async ({ action }) => {
if (action.type === "regenerate") {
chat.history.slice(0, -1);
return chat.turn();
}
if (action.type === "undo") chat.history.slice(0, -2); // edit only
},
```
Returning a `StreamTextResult`, `string` or `UIMessage` from `onAction`
is no longer supported and now fails with an error pointing to
`chat.turn()`. A response produced that way skipped every turn
guarantee, and its delivery to the browser was unreliable: the frontend
never read the stream `transport.sendAction` returned, so a regenerate
that appeared to work on the server did not render.
History edits made by an action are still persisted as before:
platform-managed snapshots are written after the edit, and apps with
their own store mirror the edit themselves.
- End a chat conversation from inside the agent with `chat.close({
reason })`. The session row is closed, further sends are refused with
HTTP 409, and the run exits without scheduling a continuation, so a
budget cap, a completed goal, or a signed-out user can stop the
conversation rather than only the current run.
([`0a23814a0`](https://github.com/triggerdotdev/trigger.dev/commit/0a23814a0896205da227520bd417bd490a017379))
```ts
chat.agent({
id: "budgeted-agent",
run: async ({ messages, signal }) =>
streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }),
onBeforeTurnComplete: async ({ chatId }) => {
if (await overBudget(chatId)) {
chat.close({ reason: "Monthly budget reached" });
}
},
});
```
The current turn still streams in full. Decide the close before the turn
ends (`run()`, `prepareStep`, `onBeforeTurnComplete`) so the closed
state rides out on that turn's final record and the user sees it as soon
as the answer finishes. `TriggerChatTransport` picks the close up from
the response stream or from a refused send, exposes it as
`transport.sessionStatus(chatId)` plus
`transport.sessionClosedReason(chatId)`, and stops sending and
reconnecting. Closing a session from outside with `sessions.close()` now
also reaches a live run, so an idle or suspended agent exits on its next
wake instead of waiting out its idle timeout. Writes to a closed
session's named side channels are refused with the same 409.
- `run()` now receives a `streamText` with your agent's managed options
already applied, so they cannot be lost by leaving out the spread:
([#4884](https://github.com/triggerdotdev/trigger.dev/pull/4884))
```ts
run: async ({ messages, signal, streamText }) =>
streamText({ model, messages, abortSignal: signal });
```
Spreading `chat.toStreamTextOptions()` still works and is equivalent.
The difference is what happens when your options collide with the
managed ones. Passing `tools` after the spread replaces the skill tools,
and passing your own `prepareStep` replaces the managed one, which
silently switches off steering, compaction and injected context. The
managed `streamText` merges tools and composes `prepareStep` instead, so
neither can be turned off by accident.
`system` can be set at the call site, on `chat.agent({ system })`, or
through `chat.prompt.set()`, but only in one of them: setting it in two
places throws, because no single shape merges two system values across
every supported AI SDK version, and dropping one silently is the failure
this seam exists to prevent. Injected instructions append to whichever
one is in play.
`chat.agent()` also takes `registry`, `cacheControl` and
`systemProviderOptions` now, so a managed prompt's model and its cache
breakpoint no longer have to be passed at the call site.
`chat.toStreamTextOptions()` applies them as well, so spreading it into
the `streamText` imported from `ai` stays equivalent to the one `run()`
receives.
`chat.headStart` and `chat.startHeadStart` hand their `run` the same
thing, carrying the options the handover protocol depends on. There it
matters more: re-setting `messages`, `prompt`, `stopWhen` or
`abortSignal` after a spread breaks the handover rather than degrading a
feature, and nothing caught it. On the managed one those four keys are a
type error; `tools` is yours to pass.
- `chat.agent` persists a conversation through a `TranscriptStorage`: an
adapter with `load` and `save` that the runtime drives after every turn,
failed turn and history-changing action. The platform snapshot stays the
default; bring your own to write the conversation to your database as it
happens. Each save carries both the changes since the last one (so a row
store writes only what changed, and an undo is one `truncateAfter`) and
the whole transcript as it now stands (so a document store writes it
as-is with no state of its own).
([#4896](https://github.com/triggerdotdev/trigger.dev/pull/4896))
```ts
chat.agent({
id: "my-chat",
storage: myTranscriptStorage,
run: async ({ messages, signal, streamText }) =>
streamText({ model, messages, abortSignal: signal }),
});
```
`chat.createLoadTranscriptAction(storage)` and `useLoadTranscript` read
the conversation back the same way for every storage, and
`runTranscriptStorageTests` from `@trigger.dev/sdk/ai/test` checks an
implementation against the contract.
Compaction summaries and `chat.inject` context now survive a
continuation run, and crash recovery runs for every agent, including one
that owns its own context. `hydrateMessages` is deprecated in favour of
`loadContext` on a storage. The snapshot format is now version 2, which
older SDK versions cannot read.
- Actions are sent through `useChat` so a turn that follows one renders
like any turn. `TriggerChatTransport` recognises `body.action` on a
`useChat` request and sends it as an action, so `sendMessage(undefined,
{ body: { action } })` or `regenerate({ body: { action } })` sends the
action and `useChat` owns the response: it streams into the message
list, `status` and `error` behave as for a message, and `stop` works.
`useChatActions({ sendMessage })` in `@trigger.dev/sdk/chat/react` is a
two-line convenience over that.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
```tsx
const { sendMessage } = useChat({ id: chatId, transport });
const { sendAction } = useChatActions({ sendMessage });
sendAction({ type: "regenerate" });
```
Previously the frontend docs said `useChat` consumed the stream
`transport.sendAction` returns; it never did, so an action's answer was
never rendered by an app following them. `transport.sendAction` still
returns a stream that callers outside `useChat` must read, and now
accepts `{ abortSignal, metadata }`, with per-action metadata merged
over the transport's `clientData`.
- Trigger.dev now uses Zod 4 by default. Projects using Zod 3.25.56 or
later 3.x releases remain supported.
([#4039](https://github.com/triggerdotdev/trigger.dev/pull/4039))
Zod remains a runtime dependency of packages that execute schemas, so
existing and new installations continue to receive it automatically. The
matching peer dependency range allows package managers to reuse either a
compatible Zod 3 or Zod 4 installation from your project.
### Patch Changes
- Chat sessions can now be pinned to a deployment, so a conversation
keeps talking to the agent version its release shipped with, and follows
the pin on its own when your app redeploys. Opt out with `triggerConfig:
{ externalDeploymentId: null }` or `versionSkew: "hold"`. Also fixes
`AgentChat` ignoring `maxDuration`, `region` and `lockToVersion`, and a
restored `AgentChat` session never picking up a new deployment id.
([`1133ad45e`](https://github.com/triggerdotdev/trigger.dev/commit/1133ad45e7c1edbc2ff053678e37146c155521ef))
- `chat.agent`: a `chat.history` edit made in `onTurnComplete` after a
failed turn is now kept. Previously the edit was applied only when the
turn succeeded, so a failure record or a card the hook closed on the
error path never reached the transcript.
([`3f67c71c0`](https://github.com/triggerdotdev/trigger.dev/commit/3f67c71c0c68ea53ba1859b4e31054cc5d52293a))
- `chat.agent`: after a Head Start turn whose handed-over tool call was
followed by more tool steps, the next turn no longer fails with
`tool_use ids must be unique`. The runtime kept the warm step's pending
tool call in the model context alongside the completed response that
already contained it.
([`8b72e6c06`](https://github.com/triggerdotdev/trigger.dev/commit/8b72e6c0616b1570d57f35b5e2a736791ad3c6f0))
- `chat.agent`: a continuation boot no longer re-dispatches the message
that resumed it, and a turn with no new user message no longer calls the
model. Previously a resumed run could answer the same message twice, and
the second attempt failed against providers that reject a trailing
assistant message, overwriting an answer that had already completed.
([`35e57e785`](https://github.com/triggerdotdev/trigger.dev/commit/35e57e785c6c80ee22329c597287ff88a3486e4e))
- `chat.agent`: a run that recovers a session with more than one
in-flight user message no longer drops the unanswered ones if it
restarts mid-recovery. Recovered messages now hold the resume cursor
until each has been answered, so a restart re-answers the rest instead
of resuming past them. Previously the cursor could advance past messages
that were only held in memory, so a crash before they were dispatched
lost them.
([#4907](https://github.com/triggerdotdev/trigger.dev/pull/4907))
- Reading a page of a chat agent's conversation no longer downloads the
whole conversation. The saved transcript now carries an index, so asking
for the most recent messages fetches only those messages, and history
loads in roughly constant time however long the chat gets.
([`b7e86f2af`](https://github.com/triggerdotdev/trigger.dev/commit/b7e86f2afe1b1b4e38f1f2f00222eb172e2d0ee3))
A paged read also returns only the conversation itself. The model-side
context an agent keeps, its compacted history and any injected context,
is no longer included, so it cannot reach a browser through a
load-transcript server action.
The built-in storage is deliberately basic about long conversations:
once an agent has compacted, it keeps roughly the last hundred messages
and drops the rest, so what it rewrites each turn stops growing. A
conversation that never compacts is kept whole. If your app renders
history further back than that, give the agent your own transcript
storage.
The saved format has changed and an older SDK cannot read it, so a
deployment rolled back to an earlier version will not find a readable
transcript for conversations the newer version already saved, and those
conversations continue from the live stream tail instead. Roll forward
rather than back, or keep your own transcript storage.
- `useTriggerChatTransport` now picks up changes to `accessToken`,
`startSession` and `fetch` on re-render, so a chat that stays mounted
while the surrounding page changes no longer keeps sending to the
endpoint captured on first render.
([`9ae9c1ae4`](https://github.com/triggerdotdev/trigger.dev/commit/9ae9c1ae43a91e21afcc6e2c97c4b63b9b0bff71))
- Steering messages are now kept in the conversation when you drive
turns yourself with `chat.createSession()` or `chat.MessageAccumulator`.
Previously a message that arrived mid-answer shaped that answer and then
existed nowhere: it was missing from `turn.uiMessages`, so an app
persisting from there never stored it, missing from `turn.messages`, so
every later turn answered as though it had never been sent, and it was
not queued as its own turn either. It now lands in both, the same way it
does on `chat.agent`.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
- Injected system context is merged into a single instruction block, so
it works on every supported AI SDK version. Note that a cached system
prompt gives up its cache entry for as long as an injection is live,
since the cached prefix has changed.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
- `chat.inject()` with `role: "system"` now works. It previously put the
system message into the conversation, which AI SDK 7 rejects for every
provider: the next turn died with a generic "An error occurred." and
persisted an empty assistant message, so the agent looked like it had
stopped answering. System-role context is now appended to the model's
instructions, which is also the only way to inject context the agent
treats as trusted.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
Two things to know. Instructions are delivered by
`chat.toStreamTextOptions()`, so a `run()` that calls `streamText`
without spreading it does not receive a system-role injection. The
conversational lane has no such requirement. And an injection applies to
the next turn only, rather than repeating on every turn that follows it.
Every inference call in that turn sees it, so a `run()` that builds
options more than once gets the same instructions each time. An
instruction injected after an action has run, and before the next
message, reaches that next turn rather than the one after it.
- A failed write to a realtime or chat session stream no longer crashes
the process running it, and a dropped chat session output write is now
logged instead of swallowed.
([`fb25c0149`](https://github.com/triggerdotdev/trigger.dev/commit/fb25c0149c6c734f942f6f41210b197ed4b1f736))
- Undo, edit and regenerate now survive a run ending. History rolled
back from `onAction` was only kept in the running worker's memory, so
the rollback held while that worker stayed warm and then reverted on the
next continuation. The undone messages came back, minutes later, with no
error. This also holds when the turn before the action failed: the
rollback used to be written against the cursor from before that turn, so
a continuation could replay output the failed turn had already
superseded.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
- Reduce sensitive values in CLI and SDK diagnostics, secure files
created by `trigger env pull`, and remove credentials from collected Git
remote metadata.
([`ff05824c1`](https://github.com/triggerdotdev/trigger.dev/commit/ff05824c1bdf1c2276d202ed84328c948cc290a3))
- Server-side `AgentChat` streams now reconnect when the connection
drops mid-turn instead of ending with a truncated reply, and a turn that
still cannot be resumed ends with an error rather than a silent
truncation.
([`8bf27a629`](https://github.com/triggerdotdev/trigger.dev/commit/8bf27a62937b5858f4963d65a9d7802982f1724e))
- Session public tokens can now be narrowed to one stream: `read: {
sessions: "chat_123:out" }` grants read access to that session's `.out`
channel only, without access to the session record or its other
channels.
([`33cf5701b`](https://github.com/triggerdotdev/trigger.dev/commit/33cf5701b4536012d45e365761c4a36067ea5f1d))
- Fixes storage of large trigger payloads for task ids containing a
slash, which could fail the trigger with an "Invalid packet storage
path" error. It affected ids that started or ended with a slash,
contained two slashes in a row, or contained a `.` or `..` path
component. The storage path is now built from a generated id rather than
from the task id, so no task id can produce an unusable one, and
payloads that are already stored are still read from where they were
written.
([`ed37e19c9`](https://github.com/triggerdotdev/trigger.dev/commit/ed37e19c9f70495ba2a067245f8a4a83aaace2c3))
- Steering messages injected mid-answer are now part of the
conversation, both for your hooks and for the model on later turns.
Previously they reached the model for the answer they steered and
reached the browser, but nothing else: `onTurnComplete` never saw them,
so an app storing its own transcript lost the instruction the answer was
shaped by, and it vanished from the conversation on reload. The model
also forgot the instruction from the next turn onwards, answering as
though the message had never been sent, while the chat UI still showed
it. This holds when the steered turn fails part-way, and when
`pendingMessages.prepare` reshapes the message: later turns now see the
same form the steered turn did, not the original message.
([#4816](https://github.com/triggerdotdev/trigger.dev/pull/4816))
Approving a tool call no longer undoes compaction. A tool-approval
continuation used to rebuild the model's context from the full
conversation, so a chat that had been summarised to fit the context
window was sent the whole transcript again on the next call, and could
go over the limit it had just been compacted to avoid.
If you worked around this by saving steering messages as they arrive, in
`pendingMessages.onReceived` for example, that write now duplicates the
one you get from `newUIMessages`. Drop it, or skip messages you have
already stored.
- Reloading a chat while the agent is still answering now shows the
message being answered. Previously the incoming message was only
persisted once the turn finished, so a refresh mid-answer showed the
reply arriving with no question above it.
([`986811008`](https://github.com/triggerdotdev/trigger.dev/commit/9868110089cb08817801bc4e1026dd6c781be1be))
Adds `chat.deferBeforeOutput()` for app-owned writes that the next page
load has to see. Like `chat.defer()` the work is not awaited by the hook
that registers it, so it runs alongside the model and costs no time to
first token, but the answer is held until it lands. Use it for the
conversation or message write you previously had to `await` in
`onTurnStart`, as long as nothing else in the turn reads that write
back: it orders the write against what the frontend can see, not against
the model, so a tool that reads the same row still needs an awaited
write.
- chat.agent transcript fixes: a turn that errors before the model
produces any content no longer stores an empty assistant message, an
error thrown without a message now shows a generic error instead of a
blank one, and a custom transcript storage no longer needs to preserve
exact message JSON for a compaction to survive a continuation.
([#4910](https://github.com/triggerdotdev/trigger.dev/pull/4910))
- Updated dependencies:
- `@trigger.dev/core@4.6.0`
## @trigger.dev/python@4.6.0
### Patch Changes
- Updated dependencies:
- `@trigger.dev/sdk@4.6.0`
- `@trigger.dev/core@4.6.0`
- `@trigger.dev/build@4.6.0`
</details>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…e, fullscreen blocks and queue charts Mono-RevId: 7a958bdcafd56dd32ed248d8928893400dbaac6b
**Before:** the "Why are you thinking of downgrading?" dialog always listed its six reasons in the same fixed order, so options near the top were more likely to be picked than the ones at the bottom. **After:** the same six reasons appear in a random order, reshuffled on each page load. The free-text box below the list is unchanged. How: the list is lifted to a module-level constant and shuffled once per mount with a Fisher-Yates shuffle over a copy, through a `useState` initialiser so ticking a checkbox does not reorder the list mid-interaction. Answers are submitted by value rather than by position, so the reordering does not change the submitted payload; the checkbox ids now derive from the label instead of the index. Mono-RevId: 79ad55c0c74576161864d85f1d1eb07891499ede
Deployments now show the runtime their worker actually reported. Bun deployments were being labelled "Node.js" because the runtime recorded against the deployment was usually empty and the dashboard filled the gap with a default. A deployment with no recorded runtime now shows a dash instead of a guess. Fixes #3105. Mono-RevId: b68e8835841d7a47c44ed9bfd6a0927ab6ca0edf
Makes additional API key creation and authentication always available by removing the completed rollout controls. Also removes the disabled-path telemetry and flag-specific tests. Mono-RevId: c8c33fcc4d127c3d99b9380b826b3989ae284244
Mono-RevId: dab37c34d7f8fb423e9e41b5cf05baa9a7751d42
Mono-RevId: f38e17c1e25cc4c4a6d2f38ee38cac7e2722e50b
Mono-RevId: 64a960c72b0bc5f14e8a1536cc523bb726b768f1
Prevents the external deployment parking test from racing with worker setup's automatic background scheduling. The test now suppresses helper-generated jobs and invokes the resolver only after both deployment candidates exist. Verified with 10 consecutive targeted test runs. Mono-RevId: 1c2d072a208ea0eda1c47653d1346d01f0a7b9a5
… poll Mono-RevId: dd197d9c6b561976f0a005411fa44c4bdc8e799c
The CLI can now manage projects, runs and environment variables without leaving the terminal. - `trigger projects create|get|rename` - create a project, inspect one or rename it - `trigger runs list|get|replay|cancel` - list runs with filters, inspect a single run, replay it or cancel it - `trigger env set` - set or update an environment variable All of these build on API endpoints that already exist, so no server changes are needed. Each command group has a new docs page in the CLI reference. Mono-RevId: 3993037b846add708191cfefc88a52242a278a9a
…le model overrides and a managed summary prompt feat(dashboard-agent): move the agent to Claude Sonnet 5, with per-role model overrides and a managed summary prompt The in-dashboard agent now answers with Claude Sonnet 5 for its main turns, the code and watch prompts, the warm first-turn step, compaction summaries, attention wakes and the turn eval judge. Claude Haiku 4.5 stays on chat titles. - Adds the Bedrock inference profile mapping for Sonnet 5 and keeps the Sonnet 4.6 entry so stored prompt versions that still name it resolve. - Switches thinking off for the bounded calls (summaries, attention wakes) so their output caps are all answer. - Main turns and the warm first-turn step pass each model's documented output ceiling explicitly, so an unknown-to-the-provider id no longer falls back to a 4096-token cap. - The compaction summariser is a managed prompt (`dashboard-agent-summary`), so its text and model can be versioned and overridden from the dashboard like the system prompt. - Each role's model can be overridden per environment with `DASHBOARD_AGENT_MODEL`, `DASHBOARD_AGENT_SUMMARY_MODEL`, `DASHBOARD_AGENT_JUDGE_MODEL` and `DASHBOARD_AGENT_TITLE_MODEL`, with the above as defaults. - No dependency changes; the installed providers accept the new id. Mono-RevId: c01a1daba486863c535fede891d96259fa6b7386
…ate schema Fix the MCP server failing every `tools/list` request with "Date cannot be represented in JSON Schema". A tools/list call serializes every tool's input schema in one batch, so a single unserializable type fails the whole listing and the server appears to expose no tools. The `delay` field on `trigger_task` used `z.coerce.date()`, which Zod 4 refuses to represent in JSON Schema where Zod 3 approximated it as a date-time string. Replaced with `z.iso.datetime()`, which keeps the date-time shape in the tool contract. The date branch was already unreachable, since `z.string()` matches first and MCP arguments always arrive as JSON. Adds a test that serializes every exported MCP schema to JSON Schema, to catch this class of failure. Closes #4935 Mono-RevId: 13ad309baa6faba1695cba61f1e959cfce8c0e4b
Mono-RevId: 7a492cae1766e26c358e150051c10c1aa49d9c28
## Summary 1 new feature, 4 improvements, 2 bug fixes. ## Improvements - Show schedule policy warnings during development and deployment, and present Free plan schedule limits without internal stack traces. ([`723fde4a8`](723fde4)) - New CLI commands: `projects create/get/rename`, `runs list/get/replay/cancel`, and `env set` ([`5bcd1a9fa`](5bcd1a9)) - Warn when a deployment uses the deprecated Node.js 21 runtime. Deployment logs now include upgrade guidance, and the account associated with the deployment receives an email notification. ([`053b0b4c6`](053b0b4)) ## Bug fixes - Fix the MCP server failing every `tools/list` call with "Date cannot be represented in JSON Schema" ([`6c90639f2`](6c90639)) ## Server changes These changes affect the self-hosted Docker image and Trigger.dev Cloud: - The assistant in the dashboard now answers with cards: investigation cards for run questions, carrying the run's timeline and links to the run, queue or deployment, alongside charts and tables you can open fullscreen or copy — including queue metrics — and it answers about another project or environment without you switching to it first. The assistant reads run timelines through a dedicated endpoint, so span durations and launch events are consistent on every event store; the public run trace API is unchanged. - Warn when Production projects still use Node.js 21 and link directly to update instructions - Deployments now show the runtime their worker actually reported, so Bun deployments are no longer labelled Node.js; a deployment with no recorded runtime shows a dash instead of a guess. <details> <summary>Raw changeset output</summary> # Releases ## @trigger.dev/build@4.6.1 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.6.1` ## trigger.dev@4.6.1 ### Patch Changes - Show schedule policy warnings during development and deployment, and present Free plan schedule limits without internal stack traces. ([`723fde4a8`](723fde4)) - New CLI commands: `projects create/get/rename`, `runs list/get/replay/cancel`, and `env set` ([`5bcd1a9fa`](5bcd1a9)) - Fix the MCP server failing every `tools/list` call with "Date cannot be represented in JSON Schema" ([`6c90639f2`](6c90639)) - Warn when a deployment uses the deprecated Node.js 21 runtime. Deployment logs now include upgrade guidance, and the account associated with the deployment receives an email notification. ([`053b0b4c6`](053b0b4)) - Updated dependencies: - `@trigger.dev/core@4.6.1` - `@trigger.dev/build@4.6.1` - `@trigger.dev/schema-to-json@4.6.1` ## @trigger.dev/core@4.6.1 ### Patch Changes - Show schedule policy warnings during development and deployment, and present Free plan schedule limits without internal stack traces. ([`723fde4a8`](723fde4)) ## @trigger.dev/python@4.6.1 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.6.1` - `@trigger.dev/build@4.6.1` - `@trigger.dev/sdk@4.6.1` ## @trigger.dev/react-hooks@4.6.1 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.6.1` ## @trigger.dev/redis-worker@4.6.1 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.6.1` ## @trigger.dev/rsc@4.6.1 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.6.1` ## @trigger.dev/schema-to-json@4.6.1 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.6.1` ## @trigger.dev/sdk@4.6.1 ### Patch Changes - Updated dependencies: - `@trigger.dev/core@4.6.1` </details> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Chat agent traces now show warm idle time and durable waits separately, with shorter message labels and fewer repeated session IDs. Durable wait spans open the waitpoint inspector while waiting and after completion. This improves trace visibility without changing idle settings or compute billing behavior. Mono-RevId: 1a071a992f017a495f9b0becd8511a3a1ee9a9a6
The billing limit banner now has an "No limit" button that turns the billing limit off without leaving the page, behind a confirmation. Mono-RevId: c863f19f28e5613b42883bbc2a6354513372fe00
…e chat conversation A `chat.agent` with compaction configured could summarise a conversation after its very first question. The between-turns check was handed the turn's token usage summed over every tool-calling step, so a five-step turn reported its context five times over. It now receives the last step's usage, which is the context the model held on its final call. The summed figure is still on the event as `turnUsage`. A head-start handover that completes its pending tool call under the same message id now replaces the spliced partial in the model lane directly. Before, the replacement never matched and the lane was rebuilt from the transcript with a warning, which also dropped any pending lane injections. The dashboard agent decides compaction on the whole context the provider billed for the last call, 100k tokens by default and configurable with `DASHBOARD_AGENT_CONTEXT_TOKEN_BUDGET`. There is no longer a prefix constant to keep in step with the model or the prompt. Mono-RevId: 1ec83a82ec108975607b9aea4becd61deff1b6ee
Org member invites couldn't be accepted when the invite email's casing didn't match the invitee's account email (for example, `User@example.com` vs `user@example.com`). The accept route compared emails strictly, and the pending-invite lookups used exact matching, so the invite never appeared for the invitee in the first place.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #
✅ Checklist
Testing
[Describe the steps you took to test this change]
Changelog
[Short description of what has changed]
Screenshots
[Screenshots]
💯