Commit Graph

7413 Commits

Author SHA1 Message Date
Eric Allam 0c839e8566 feat(sdk,cli): namespace agent skills with trigger- and add cost-savings (#3970)
## Summary

Three improvements to the SDK-bundled agent skills (follow-up to the
skills installer):

- **`trigger-` namespace.** The installed skills (`authoring-tasks`,
`getting-started`, …) had generic names that collide with unrelated
skills in a shared agent skills directory. They're now prefixed —
`trigger-authoring-tasks`, `trigger-getting-started`, etc. — matching
the convention the public skills repo already uses.
- **New `trigger-cost-savings` skill.** An MCP-driven cost audit:
right-sizes machines, flags missing `maxDuration`, spots sequential
triggers that could batch, and reviews schedule frequency, using
`list_runs` / `get_run_details` for live analysis.
- **Bundle the full docs.** `@trigger.dev/sdk` now bundles the entire
"Documentation" section of the docs (157 pages) instead of a curated
55-page subset, so an agent has the complete, version-pinned reference
in `node_modules`.

## How the bundling works

`scripts/bundleSdkDocs.ts` now reads `docs/docs.json`, walks the
"Documentation" dropdown, and copies every page under it into the SDK.
The set tracks the docs navigation automatically — add a page to the nav
and it ships, no skill edits needed. The API reference and Guides &
examples dropdowns are intentionally excluded. A skill's `sources:`
frontmatter is now informational only.

The dropped idea of a dedicated `trigger-config` skill is replaced by
references to the bundled build-extension docs (`config/extensions/*`)
from the `trigger-authoring-tasks` config section and the chat-agent
skills.
2026-06-16 23:27:28 +01:00
James Ritchie 5f2d437eb5 fix(webapp): Fix for task page search bar re-rendering bug (#3971)
## Summary

Typing in the search bar on the task page could clear or reset the input
mid-keystroke. This fixes the re-render race so the field stays stable
while you type.

## Root cause

Two things compounded:

- `SearchInput`'s sync effect depended on `text`, so it re-ran on every
keystroke and could overwrite the input with the URL/controlled value
while focused.
- Each task row unmounted and remounted its activity chart during the
side-panel open/close animation (25 charts at once), forcing heavy
re-renders that the search effect raced against.

## Fix

- `SearchInput` now tracks the last synced value in a ref instead of
comparing against `text`, keeping the effect off the keystroke path. It
only writes to state when the incoming URL/controlled value actually
changes, and never while the input is focused.
- Activity charts are now hidden (`hidden` attribute) instead of
unmounted during the panel animation, so the rows don't churn the tree
and the resize stays smooth.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 23:19:24 +01:00
Eric Allam e829eddd5e docs(skills): reflect the SDK-bundled, version-pinned agent reference (#3939)
## Summary

The agent skills' deep guidance now ships inside `@trigger.dev/sdk` and
is read from `node_modules`, so it tracks the `@trigger.dev/sdk` version
installed in your project automatically. This updates the Skills page,
the Building with AI step, and the rules-redirect page to drop the old
"pinned to the CLI version, re-run to refresh" framing and describe the
version-pinned reference instead.

Pairs with the SDK/CLI change in #3937. Keep this draft until that
ships, since it describes behavior that is not released yet.
2026-06-16 18:44:58 +01:00
Eric Allam 07a0e4ade9 feat(webapp): split Models into Your models and Model library tabs (#3958)
## Summary

The Models page is now split into two tabs. **Your models** shows the
models your project has actually used in the selected time range, with
usage charts (cost over time, tokens over time, calls by model), a
per-model table of calls / cost / avg TTFC / avg tokens-per-sec, and
calls/tokens trend sparklines. **Model library** is the full catalog,
reordered from alphabetical to a relevance-based provider order
(Anthropic, OpenAI, Google, then the rest), newest models first within
each provider, with a "New" badge on models released in the last 7 days.

One time-range selector drives the whole Your models tab, so the charts,
the table, and the sparklines all share the same window. Opening a model
shows its own metrics with an independent range picker and a "View in AI
metrics" link that opens the AI metrics dashboard filtered to that
model. The active tab is kept in the URL so it survives a refresh and is
shareable.

## Prompt caching & cost accuracy

Both the Your models tab and the AI metrics dashboard now surface
prompt-cache usage: a cache-savings column plus per-model cached-tokens
and cache-hit-rate views, and a caching section on the dashboard (hit
rate, cached tokens, estimated savings, and hit rate by model).

Building this surfaced a cost bug. `input_tokens` is the total prompt
count and already includes cache-read and cache-creation tokens, but the
cost pipeline charged the full input at the input price and then added a
separate cache line, so cached tokens were billed twice (and on
Anthropic, cache reads were never discounted because their price is
keyed differently). The input price now applies only to the non-cached
remainder, with cache prices resolved across the provider-specific keys,
so LLM cost and the cache hit-rate metric are accurate. Hit rate is
computed as cached reads over total input.

## Notes

Also fixes React "invalid DOM property" console warnings from the
provider icons (the Llama and DeepSeek SVGs used raw `fill-rule` /
`clip-rule` / `clip-path` attributes), which this page surfaces by
rendering more provider icons.

## Screenshots

**Your models tab:** usage charts and a per-model table with
calls/tokens trend sparklines.

<img width="2560" height="1267" alt="1-your-models-tab"
src="https://github.com/user-attachments/assets/859bd24f-9047-4828-8bbb-83e5882846d6"
/>


**Model library:** provider-relevance ordering with a "New" badge on
models released in the last 7 days.

<img width="2560" height="1267" alt="2-model-library-tab"
src="https://github.com/user-attachments/assets/46dd54b9-80f9-4922-ade9-5935b08dfebc"
/>


**Model detail, Metrics tab:** per-model range picker and a "View in AI
metrics" link.

<img width="2560" height="1267" alt="3-model-detail-metrics"
src="https://github.com/user-attachments/assets/0f65d9d0-6142-4918-93f0-110bb277101a"
/>


**View in AI metrics:** the dashboard deep-linked and filtered to the
selected model.

<img width="2560" height="1267" alt="4-ai-metrics-filtered"
src="https://github.com/user-attachments/assets/821f256c-e305-493c-98c7-eafaf2f57f83"
/>
2026-06-16 18:44:37 +01:00
Eric Allam 723c994547 docs(ai-chat): correct the extractNewToolResults return type (#3959)
## Summary

The "What extractNewToolResults returns" reference in the
tool-result-auditing guide did not match the SDK. It listed an `input`
field that `chat.history.extractNewToolResults()` never returns, and
marked `output` as optional when it is always present.

This corrects the block to the real `ChatNewToolResult` shape
(`toolCallId`, `toolName`, `output`, optional `errorText`). Every usage
example in the same guide already reads only those fields, so the
reference now matches both the examples and the code.
2026-06-16 18:10:19 +01:00
Eric Allam 14958009b8 docs(ai-chat): add prompt caching guide (#3951)
## Summary

New `/ai-chat/prompt-caching` guide covering how to cache a chat agent's
prompt prefix with Anthropic prompt caching: the system prompt, the
conversation history (a `prepareMessages` breakpoint), and how caching
interacts with compaction. It also shows how to verify cache hits via
usage and the dashboard, the prefix-stability footguns, and an "Other
providers" section (OpenAI and Google cache automatically; Amazon
Bedrock uses `cachePoint` through `systemProviderOptions`).

Registered under Features in the AI Agents nav, next to Compaction.

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Eric Allam <ericallam@users.noreply.github.com>
2026-06-16 18:10:02 +01:00
Oskar Otwinowski cf4aa7e918 fix(webapp): Vercel env var sync rejecting batches containing only reserved keys (#3966)
Fix Vercel onboarding wizard to properly filter out reserved TRIGGER_
env vars
2026-06-16 15:43:11 +01:00
Eric Allam 63d6432603 docs(ai-chat): headStart handover for custom agents + triggerConfig (#3964)
## Summary

`chat.headStart` now works with the `chat.customAgent` and
`chat.createSession` backends (not just `chat.agent`), and takes a
`triggerConfig` option. These docs cover both.

The Fast starts guide gets a "Handover with custom agents" section
showing how each backend consumes the handover (`consumeHandover`
returning `{ isFinal, skipped }` for custom agents, `turn.handover` for
createSession), including threading `originalMessages` so a resumed tool
round merges into the handed-over assistant. The `chat.headStart` API
section documents `triggerConfig` (tags, queue, machine, and the rest)
on the auto-triggered run.

The reference picks up `ChatTurn.handover`, `turn.complete()` with no
source, `chat.waitForHandover`, and a new `HeadStartHandlerOptions`
table.

Docs for the SDK changes in
[#3963](https://github.com/triggerdotdev/trigger.dev/pull/3963).
2026-06-16 15:39:51 +01:00
Eric Allam 2936382e3e ci: add docs-release-* tag workflow to publish docs at release (#3969)
## Summary

Docs deploy from the `docs-live` branch via Mintlify, so merging to
`main` no longer publishes docs on its own. To publish, push a
`docs-release-*` tag at the commit you want live. The workflow runs the
Mintlify broken-links check against that commit, then fast-forwards
`docs-live` to it, which is what Mintlify deploys from.

## Design

The ref move uses the GitHub API with `force=false`, making it
fast-forward only: a tag that is not ahead of `docs-live` fails the job
rather than rewinding production. Mintlify's GitHub app reacts to the
resulting push and deploys, so no extra deploy credentials are needed.

Usage:

```bash
git tag docs-release-2026.06.16   # tag the main commit you want live
git push origin docs-release-2026.06.16
```
2026-06-16 15:33:49 +01:00
James Ritchie afe6dd945d Feat(webapp): schedules fixes and UI improvement (#3965)
## Summary

Reworks the scheduled task page right-hand sidebar.

- Adds **Overview** / **Schedules** tabs. The Schedules tab is a
paginated table of all schedules attached to the task, declarative
first.
- Surfaces schedule fields (ID, CRON + human-readable description,
next/last run, status) directly in the Overview property table.
- Sidebar can be dragged much wider (up to 80% of the viewport).
- "No schedules attached" panel explains declarative vs imperative and
links to docs.
- Schedule **create / edit / enable / disable / delete** all happen
inside the existing Sheet — no more navigating to the standalone
schedule page. Toasts confirm each action.

## Test plan

- Open a scheduled task page and verify the new tabs
- Create, edit, enable/disable, and delete a schedule — confirm you stay
on the page and see a toast each time
- Visit a task with no schedules attached and confirm the info panel
renders
- Drag the sidebar wider; confirm pagination shows when there are >25
schedules
2026-06-16 15:28:37 +01:00
Eric Allam 17482c0577 feat(sdk): chat.headStart handover for customAgent and createSession (#3963)
## Summary

`chat.headStart` (the warm step-1 fast path) previously handed its
response over only to `chat.agent`. This extends handover to the other
two backends: `chat.customAgent` consumes it with
`conversation.consumeHandover({ payload })` on turn 0, and
`chat.createSession` surfaces it as `turn.handover` (call
`turn.complete()` with no source to finalize a pure-text handover). The
low-level `chat.waitForHandover()` and `accumulator.applyHandover()` are
exported for hand-rolled loops.

It also adds `triggerConfig` to `chat.headStart()` and
`chat.openSession()`, so the auto-triggered handover-prepare run
inherits tags, queue, machine, and the other session run options the
same way `chat.createStartSessionAction()` does. The `chat:{chatId}` tag
is prepended automatically. Because the session is created once on the
first head-start turn (idempotent on the chat id), this is the only
place those options can be set for a head-start chat's lifetime.

## Fix: tool-call resume

When the warm step-1 hands over a pending tool call (rather than pure
text), the agent loop resumes that tool round. For it to merge cleanly
the pipe threads the spliced partial as `originalMessages`, so the
resumed tool-output chunk attaches to the handed-over tool-call instead
of throwing `No tool invocation found`. `MessageAccumulator.addResponse`
now also dedups by id (replace-in-place), so the persisted history
doesn't carry a duplicate assistant message when the resumed response
reuses the partial's id.

Incorporates the `triggerConfig` work from
[#3933](https://github.com/triggerdotdev/trigger.dev/pull/3933) by
@saasjesus, with `createStartSessionAction` extended to also forward
`maxDuration`, `region`, and `lockToVersion` so the two session entry
points stay consistent.

Verified end-to-end against a local environment: handover (pure-text and
tool-call) on both new backends, a `chat.agent` regression pass, and
`triggerConfig` tags and queue landing on the run.

---------

Co-authored-by: saasjesus <armin@chatarmin.com>
2026-06-16 15:02:51 +01:00
Saadi Myftija 002b8458d5 feat(supervisor): verify warm-start delivery, cold-start silently lost dispatches (#3918)
⚒️ Publish Worker (v4) / build (supervisor) (push) Has been cancelled
### Problem

Firestarter's `didWarmStart: true` means the response was written to a
long-poll socket — not that the runner received it. A silently dead
poller (no FIN, e.g. a VM torn down mid-poll) leaves the dispatched run
stuck in `PENDING_EXECUTING` until the run engine's heartbeat redrive,
and each redrive burns a queue redelivery toward
`TASK_RUN_DEQUEUED_MAX_RETRIES`.

### Change

After a warm-start hit, the supervisor retains the `DequeuedMessage`
(TimerWheel, default 10s), then probes the existing `getLatestSnapshot`
API. If the run is still on the exact dequeued snapshot, no runner ever
acted — it falls through to the regular cold-create path. Recovery: ~10s
+ cold start, no new APIs, no CLI changes.

- **Double-start safe**: `startRunAttempt` runs under a per-run lock and
409s stale snapshot ids, so a reviving runner and the fallback workload
can't both execute; the loser exits before running anything.
- **Probe errors → do nothing**: healthy runners legitimately act late
during platform brownouts (nested attempt-start retries), so falling
back on uncertainty would stampede duplicates. The heartbeat redrive
stays as the backstop (also covers supervisor restarts dropping timers).
- **Off by default**: `TRIGGER_WARM_START_VERIFY_ENABLED` (+
`TRIGGER_WARM_START_VERIFY_DELAY_MS`, 1–60s, default 10s). Disabled =
complete no-op. Works for all workload managers (compute/k8s/docker)
since it hooks the shared dequeue path.
- Emits `warmstart.verify` wide events (`outcome: delivered | fallback |
probe_error`), making the silent-loss rate directly measurable.
re2-test-warm-start-verify
2026-06-16 14:14:53 +01:00
Chris Arderne 19c0763a1e chore(webapp): prevent db:seed script hang (#3962)
Currently the `db:seed` script just hangs on success.

This PR adds `process.exit(0)` to the finally block after db disconnect
so the script exits properly.

---------

Co-authored-by: Chris Arderne <chris@trigger.dev>
2026-06-16 10:45:00 +00:00
nicktrn 38f280406d chore(deps): pin js-cookie, tmp and brace-expansion (#3961)
Adds `pnpm.overrides` pinning a few transitive deps to their current
releases:

- `js-cookie` → 3.0.7
- `tmp` → 0.2.7
- `brace-expansion` → 1.1.13 / 2.0.3 / 5.0.6 (one entry per major)

Each override is scoped to the affected major range so unaffected majors
aren't dragged forward. Also drops the `fast-xml-builder` override,
which no longer resolves to anything in the tree.

Lockfile-only - no published package's dependencies change.
`js-cookie`/`tmp` parents pin ranges that can't reach the new versions
on their own, so overrides (not a plain lockfile refresh) are needed to
hold them.
2026-06-16 06:48:13 +01:00
Eric Allam ab3a1e593a docs: use one canonical definition of a Session everywhere (#3956) 2026-06-15 22:13:20 +01:00
Iss 39fca87b48 docs: add troubleshooting entry for runs not dequeuing in dev (#3955) 2026-06-15 17:43:25 +01:00
Eric Allam 709477168f fix(release-pr): stop dropping changeset entries and stripping code blocks (#3954)
## Summary

The script that generates the changeset release PR description was
silently dropping some changelog entries and stripping code examples. In
[#3932](https://github.com/triggerdotdev/trigger.dev/pull/3932), entry
[#3937](https://github.com/triggerdotdev/trigger.dev/pull/3937) was
missing entirely from the Improvements list and
[#3952](https://github.com/triggerdotdev/trigger.dev/pull/3952)'s code
block was gone, even though both were present in the raw changeset
output.

## Root cause

`parsePrBody` parsed the raw changeset body line by line:

- The dependency-bump filter matched any entry whose text *began* with a
backticked package name, so a real changelog entry like ``
`@trigger.dev/sdk` now bundles... `` got thrown out along with the
genuine version-bump lines.
- Only the first line of each bullet was kept, so fenced code blocks,
sub-bullets, and continuation paragraphs were discarded.

## Fix

Group each top-level bullet with its indented continuation (code blocks,
sub-bullets, paragraphs), dedent it, and re-emit it intact. The
dependency filter is now anchored so it only matches lines that are
*entirely* a package bump, leaving real entries that merely start with a
package name.

Verified by replaying #3932's raw body through the script: #3937 returns
to the list, #3952's code block is preserved, and #3936's sub-bullets
nest correctly under their parent.
2026-06-15 16:33:09 +01:00
Oskar Otwinowski 545ecf7beb feat(plugins): add SSO plugin contract to @trigger.dev/plugins (#3949) 2026-06-15 15:08:40 +00:00
Eric Allam 3b919994c1 feat(sdk): make the chat.agent system prompt cacheable (#3952)
## Summary

`chat.agent`'s system prompt (the `chat.prompt` text plus any skills
preamble) could not carry a provider cache breakpoint, so the largest
and most stable part of the prompt re-paid full input price on every
turn. `chat.toStreamTextOptions()` now emits the system prompt as a
structured message carrying `providerOptions` when you opt in, so a
provider can cache the system block. Without an option, `system` stays a
plain string, so existing behavior is unchanged.

## API

Three ways to opt in (most specific wins, no deep merge):

```ts
// Anthropic sugar
chat.toStreamTextOptions({ cacheControl: { type: "ephemeral" } });
// provider-agnostic (also covers Amazon Bedrock's cachePoint)
chat.toStreamTextOptions({ systemProviderOptions: { anthropic: { cacheControl: { type: "ephemeral" } } } });
// at the definition site
chat.prompt.set(SYSTEM_PROMPT, { providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } } });
```

The `cacheControl` shorthand is Anthropic-only; `systemProviderOptions`
is the general form. Pairs with a `prepareMessages` cache breakpoint to
cache the conversation prefix too.

Docs guide: https://github.com/triggerdotdev/trigger.dev/pull/3951
2026-06-15 15:54:45 +01:00
Katia Bulatova 530b388fc5 feat(webapp): hide self-serve billing UI for managed-billing orgs (#3898)
### Summary 
Self-serve billing UI is now hidden for managed-billing organizations.

Plan pickers, upgrade actions, billing alerts, and related upgrade
prompts are replaced with a "Contact us" option where appropriate.

Uses the new showSelfServe subscription flag, defaulting to true for
existing self-serve organizations.

### Testing

- [x] billing pages render correctly for self-serve organizations.
- [x] managed-billing organizations no longer see self-serve upgrade
flows.
- [x] "Contact us" actions are shown instead of upgrade actions where
applicable.

### Changelog

Hide self-serve billing flows for managed-billing organizations behind
the new showSelfServe subscription flag.
2026-06-15 14:29:43 +02:00
Daniel Sutton 1cf56e5d29 ci: gate optional publish/notify jobs behind repository variables (#3950)
## Summary

Several optional workflow jobs fail on forks and private mirrors that
lack org-specific secrets or registry permissions. This adds per-job
repository-variable gates so those deployments can switch them off
without editing workflows — matching the pattern from #3901
(`ENABLE_CLAUDE_CODE` / `ENABLE_WORKFLOW_SECURITY_SCAN`).

Two variables, both **default-enabled** (a job runs unless its variable
is explicitly `'false'`), so canonical-repo behaviour is unchanged where
the variables are unset:

**`ENABLE_HELM_PRERELEASE`** — gates the chart-publish jobs that push to
`oci://ghcr.io/<owner>/charts` (needs `write_package` on the owner's
charts namespace):
- `helm-prerelease.yml` → `prerelease` job
- `release-helm.yml` → `release` job

Without the permission these fail with `403: denied: permission_denied:
write_package` on every PR / `helm-v*` tag. The `lint-and-test` jobs
(lint + template + kubeconform, no push) always run, so chart validity
is still enforced everywhere.

**`ENABLE_DEPENDABOT_ALERTS`** — gates the Dependabot notifier crons
that need `DEPENDABOT_ALERTS_TOKEN` / `SLACK_BOT_TOKEN` and post to a
specific Slack:
- `dependabot-critical-alerts.yml` → `alert` job (daily cron)
- `dependabot-weekly-summary.yml` → `summary` job (weekly cron)

On a fork/mirror these otherwise fire on schedule and fail (or post
nowhere) indefinitely.

## Test plan

- Variables unset (default): all jobs run as today.
- `ENABLE_HELM_PRERELEASE=false`: helm `lint-and-test` runs, publish
jobs skip — no 403 on repos lacking `write_package`.
- `ENABLE_DEPENDABOT_ALERTS=false`: the two cron jobs skip cleanly
(neutral, not failed).

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:41:16 +01:00
James Ritchie af526dea18 feat(webapp): chat AI UI improvements, new task landing pages and side menu (#3941)
Major dashboard restructure plus the new task landing pages and
self-serve schedules add-on integration.

## Side menu

- Full restructure: standalone Tasks / Runs / Sessions block at the top;
new collapsible sections for AI, Observability, Deployments, Manage
- Persisted collapse state per section in `dashboardPreferences`
- New / updated icons across the menu
- Dashboards section: built-in Run metrics + AI metrics + custom
dashboards, with drag-to-reorder via ReactGridLayout
(`DashboardList.tsx`)
- DevPresence connection indicator in the env selector (DEV + V2)

## Tasks (`_index` — unified Tasks page)

- Replaces the separated Agents / Standard / Schedules listing pages
with one table
- New `UnifiedTaskListPresenter` composes `TaskListPresenter` +
`AgentListPresenter` (shared `currentWorker` lookup)
- Columns: Type (with kind badge), ID, File, Running (numeric for tasks;
running + suspended pills for agents), Activity (24h stacked-by-status),
sticky menu
- Search + "Task type" multi-select filter (URL-synced)
- Client-side pagination at 25/page
- Right-hand "useful links" panel (cookie-persisted state)
- Live-reload SSE: page revalidates on `WORKER_CREATED` so onboarding
`trigger dev` flips the blank state automatically

## Agent landing page (`/agents/$agentParam`)

- New per-agent detail page
- Top tabs (Sessions / Runs) toggle both the chart panel and the table
- Three dashboard-style chart cards: Sessions/Runs activity, LLM spend,
Tokens
- `AgentDetailPresenter` queries ClickHouse for run activity, session
activity (with FINAL on `sessions_v1`), and LLM cost/token activity from
`llm_metrics_v1`
- TimeFilter at the top drives all three charts
- Sticky table header, resizable horizontal handle, sidebar with Test
agent button + properties
- Docs link → `ai-chat/overview`

## Standard Task landing page (`/tasks/standard/$taskParam`)

- New per-task detail page mirroring the Agent layout
- `TaskDetailPresenter` for activity + properties
- Chart panel wrapped in a Card with "Runs by status" header
- Top bar with title, TimeFilter, pagination
- Right sidebar: Test task + identifier, queue, machine, retry, TTL,
payload schema, etc.

## Scheduled Task landing page (`/tasks/scheduled/$taskParam`)

- New per-task detail page mirroring the Agent / Standard layout
- Top-bar actions (right → left): pagination, Bulk replay…, View all
runs, TimeFilter, Create schedule
- Connected schedules mini-table in the sidebar
- **Self-serve schedules add-on integration** (reincarnated from the
now-removed `/schedules` listing page during the `origin/main` merge):
- Bottom usage bar pinned via `grid-rows-[auto_1fr_auto]` — progress
ring + "X/Y of your schedules" + Purchase / Upgrade / Request CTA
  - At-limit "Create schedule" intercept dialog
- `PurchaseSchedulesModal` extracted as a shared component
(`apps/webapp/app/components/schedules/PurchaseSchedulesModal.tsx`)
handling increase / decrease / above-quota / need-to-delete states
- New resource action route at
`/resources/orgs/$organizationSlug/schedules-addon`

## Sessions

- Index page: list, filters, blank state, help tooltip rework
- Detail page: combined input/output chronological view (replaces split
tabs)
- Improved raw-message view layout (full-height)
- AI payload UI: `data-*` parts grouped under "AI SDK data parts:" label
- `toSafeUrl` helper guards rendered URLs from streamed content
- Fix: duplicate assistant content on inspector tab switch

## Playground (Test agent)

- Restructured top menu; back button + agent-selector popover
- Improved blank state
- Recent agent chat history moved into the tabbed menu
- Better message-scroll container (full height)

## Dashboards

- New Dashboards landing page (`/dashboards`) — Run metrics, AI metrics,
Create your own CTAs
- `BuiltInDashboards` updated; new `TasksDashboardPresenter` for the
tasks overview
- Custom dashboards section gains drag-to-reorder; cosmetic fix for
active-row drag-handle blending

## PageHeader / shared primitives

- `PageTitle` gains an `accessory` prop supporting string (auto-wrapped
in tooltip) and ReactNode
- Help tooltips on Tasks, Runs, Sessions PageTitles explaining the
concept and sub-categories
- `Card` primitive used for dashboard-style chart panels throughout

## Code review fixes (last batch on this branch)

- ClickHouse activity queries hardened: `FINAL` + `_is_deleted = 0` on
`task_runs_v2` (ReplacingMergeTree); `organization_id` + `project_id`
filters for sort-key prefix; `inserted_at` partition filter on
`llm_metrics_v1`
- `UnifiedTaskListPresenter`: shared `currentWorker` lookup;
slug-collision guard in `mergeRunningStates`; off-by-one fixed in 24h
bucket alignment
- `ScheduleListPresenter`: halved platform RPCs by deriving limit from
`currentPlan` instead of calling `getLimit`
- Sessions detail: stopped IntersectionObserver / scroll listener
re-attach on every chunk; `requestAnimationFrame` deferral on
auto-scroll to avoid virtualizer race
- URL hardening: `?types=` validated against known kinds; new
`parseFiniteInt` helper applied to `from`/`to`/`page` params
- AgentView: HITL resolution buffer now cleared once parts reach a
terminal state (was an unbounded Map on long sessions); subscription
effect deps documented with eslint suppression
- `PurchaseSchedulesModal`: bundle state resets on each open instead of
persisting stale drafts

## Manual testing

Manual smoke-test plan is tracked under
[TRI-10883](https://linear.app/triggerdotdev/issue/TRI-10883), broken
into 20 sub-issues covering onboarding, self-serve schedules, side menu,
the four landing pages, sessions, runs, dashboards, regressions and
performance.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-15 12:10:50 +01:00
Daniel Sutton b7ef51d763 fix(webapp): make SDK bundle-docs build step work in pruned Docker image (#3947)
## Summary

The webapp Docker image build runs `pnpm run build --filter=webapp...`,
which builds `@trigger.dev/sdk` as a dependency. The SDK's `build`
script recently gained a `bundle-docs` step (`tsx
../../scripts/bundleSdkDocs.ts`), but the build couldn't run it in the
pruned image, breaking the image build.

Two things were missing:

- `docker/Dockerfile` copied `scripts/updateVersion.ts` into the builder
stage but not `scripts/bundleSdkDocs.ts`, so the step failed with
`ERR_MODULE_NOT_FOUND`.
- Even with the script present, the repo-level `docs/` tree it reads is
a separate workspace package that isn't in webapp's dependency graph, so
`turbo prune --scope=webapp` excludes it — the script's missing-docs
guard would then fail the build.

## Design

The Dockerfile now copies `bundleSdkDocs.ts` alongside
`updateVersion.ts`. `bundleSdkDocs.ts` skips gracefully when the repo
`docs/` tree is absent, which is exactly the pruned-dependency-build
case (the SDK is compiled there but never published). Publishing always
runs from the full monorepo where `docs/` exists, so the missing-docs
guard still protects releases — it only fires when `docs/` is present
but a cited doc is genuinely missing, rather than when the whole tree
was pruned away. This avoids dragging 27M of docs into a throwaway
builder stage.

## Test plan

- [x] `bundle-docs` from the full monorepo still bundles all cited docs
(exit 0)
- [x] Simulated pruned tree without `docs/` skips cleanly instead of
failing
- [ ] Webapp Docker image build succeeds in CI

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:59:39 +00:00
Eric Allam ef998a518b fix(webapp): make native realtime change publishing fail-safe (#3946)
Two defensive fixes to the native realtime backend's run-change
publishing (behind a feature flag, off by default), so turning it on can
never destabilize the run lifecycle.

**Never throws at the caller.** Publish sites run synchronously on the
run-engine event bus and the metadata flush loop. The internal publish
was already wrapped in try/catch, but lazy construction (singleton +
metrics) and record encoding ran before that guard, so a throw could
propagate into a run lifecycle operation. The public
`publishChangeRecord` / `publishManyChangeRecords` helpers now wrap the
whole call and log-and-drop on failure.

**Bounds outage buffering.** The publisher connection caps
`maxRetriesPerRequest` at 1 (vs ioredis's default of 20), so during a
pub/sub Redis outage a publish rejects after ~1 reconnect cycle instead
of holding commands in memory for ~20s. A dropped publish is
latency-only, since the consumer has a periodic backstop full-resolve.
The offline queue stays on, so the first publish after a process boots
still flushes once the connection is ready.
2026-06-15 11:55:49 +01:00
Daniel Sutton f073d8708a ci: gate optional Claude and security-scan jobs behind repository variables (#3901)
## Summary

Add per-job `if:` gates so deployments that don't want — or can't run —
these jobs can switch them off via repository variables, without editing
workflows.

- `ENABLE_CLAUDE_CODE` gates the Claude jobs: interactive `@claude`, the
CLAUDE.md audit, and the REVIEW.md drift audit.
- `ENABLE_WORKFLOW_SECURITY_SCAN` gates the Zizmor job, which uploads
SARIF and so needs GitHub code scanning enabled.

Both default to **enabled**: a job runs unless its variable is
explicitly set to `'false'`, so behaviour is unchanged anywhere the
variables are unset. The sibling `actionlint` job and the report-only
Trivy scan are untouched.

## Test plan

- [x] `actionlint` clean on the four edited workflows
- [x] YAML parses for all four files
2026-06-15 11:49:51 +01:00
Daniel Sutton a7312b1b86 fix(webapp): stop logging expected auth/restore conditions as errors (#3931)
Two expected, non-failure conditions were being logged at `error` level,
which surfaces them as exceptions in error tracking and adds noise
without signal. This downgrades both to `warn`. The first is the
checkpoint-restore path when a `RESTORE` event already exists for a
checkpoint — a benign idempotency skip on a duplicate or retried event.
The second is the `/api/v1/token` endpoint when the authorization code
is invalid or expired, which is the expected steady state while the CLI
polls the endpoint during login; genuinely unexpected failures there
still log at `error`. No behavior or response changes — the token
endpoint still returns 400 in the same cases.
2026-06-15 10:54:22 +01:00
Eric Allam 911a1cff80 docs: document the Sessions HTTP API (reference, channels, scopes) (#3942)
## Summary

Documents the Sessions HTTP API for non-SDK and server-to-server
callers, which until now appeared only in the conceptual
[ai-chat/sessions](https://trigger.dev/docs/ai-chat/sessions) page.

## What's covered

- **Sessions API reference** —
`create`/`list`/`retrieve`/`update`/`close` added to the OpenAPI spec
and a new "Sessions API" group (`management/sessions/*`), mirroring the
Runs API.
- **Channel endpoints** — a reference page for the `.in`/`.out` realtime
HTTP endpoints (append, SSE read, records drain), the wire protocol,
`Last-Event-ID` resume, and the per-direction auth boundary (`.out`
append is secret-key only).
- **Session scopes** — `read:sessions:{id}` / `write:sessions:{id}` in
the authentication docs, with the capability boundary and the 1h token
TTL.

Cross-linked with the SDK-side `ai-chat/sessions` page. Verified by
rendering each page on the Mintlify dev server.
2026-06-14 21:07:12 +01:00
Eric Allam e092919c3f feat(sdk,cli): bundle agent skills + docs in the SDK for zero-drift (#3937)
## Summary

`@trigger.dev/sdk` now ships the Trigger.dev agent skills and a curated
snapshot of the docs those skills cite. The skills that `trigger skills`
installs into your coding agent are thin pointers that read this bundled
content from `node_modules`, so the guidance always matches the SDK
version installed in your project. Previously the full skill text was
copied into your repo at install time and went stale until you
reinstalled after an upgrade.

## How it works

The SDK's `files[]` now includes `skills/` (the full skill text) and
`docs/` (a curated snapshot generated at build time). The docs manifest
is derived from each skill's own `sources:` frontmatter, so a skill only
ships the docs it references, and a skill that cites a missing doc fails
the build.

The CLI installs thin skills whose body points the agent at
`node_modules/@trigger.dev/sdk/skills/<name>/SKILL.md` and
`node_modules/@trigger.dev/sdk/docs/`. They keep the high-value "Common
mistakes" anti-patterns inline so the trigger and the guardrails survive
even if the agent does not follow the pointer. `getting-started` stays
self-contained in the CLI because it runs before the SDK is installed.
2026-06-14 11:00:50 +01:00
Eric Allam 1f1a3666ee fix(sdk): custom agent loop parity for continuations, steering, and subtasks (#3936)
## Summary

Three fixes that bring custom agent loops (`chat.customAgent`
hand-rolled loops and `chat.createSession`) up to the behavior
`chat.agent` users already get, and that the docs already promise:

- **Continuation runs no longer replay already-answered messages.** A
chat continuing after a cancel, crash, or upgrade re-delivered every
prior user message into the loop's first wait, so the model re-answered
an old message while the real new one had to arrive via steering. The
`.in` resume cursor is now seeded before any listener attaches, using
the same boot logic as `chat.agent`.
- **Mid-stream steering no longer wipes the in-flight response.**
`chat.pipeAndCapture` (also backing `turn.complete()`) streamed without
a server-generated message id, so a `prepareStep` injection regenerated
the assistant id mid-stream and the frontend replaced the partial
message, discarding everything streamed before the injection.
- **Task-backed tools now work from custom agent loops.** A child task
triggered via `ai.toolExecute` failed with "chat.agent session handle is
not initialized" because the parent's chatId only threaded from the
per-turn context that hand-rolled loops never set. It now falls back to
the session handle the `chat.customAgent` wrapper binds at run boot, so
children can stream progress into the chat with `chat.stream.writer({
target: "root" })` (the documented sub-agent pattern).

## Root cause on the replay fix

Attaching any `.in` listener (`chat.createStopSignal`,
`chat.messages.on`, the first wait) opens the SSE tail with
`Last-Event-ID` taken from the seq cursor at attach time. Custom loops
attached before any cursor existed, so S2 replayed from seq 0. The fix
resolves the cursor from the latest turn-complete header and seeds both
manager cursors (`setLastSeqNum` drives the SSE resume point,
`setLastDispatchedSeqNum` gates waiter dispatch) before attach;
`chat.createSession` now creates its stop signal lazily on the first
iteration, after the seed. Seeding only the first cursor after attach
does not work, which is why the earlier attempt at this was reverted.

All three were reproduced red-green against the references ai-chat
project: the replay repro showed the continuation wait consuming a stale
message in 403ms with the real message arriving via steering injection;
post-fix the wait consumes the real message directly with no injection.
Steering now preserves the full in-flight response, and the deepResearch
sub-agent streams its progress parts into a raw-loop parent. Existing
behavior verified unchanged: full SDK unit suite, `chat.agent` steering,
and stop-then-continue on `chat.createSession`.
2026-06-14 10:58:55 +01:00
Katia Bulatova 85d93ffe0e perf(webapp): skip queue search count (#3925)
### Summary

Queue searches previously executed both a count query and a page query
with identical filters. This PR switches filtered searches to `hasMore`
pagination, removing the extra count query while preserving existing
search behavior.

### Testing

cd apps/webapp && pnpm run test ./test/queueListSearch.test.ts --run
passes


### Changelog

Improve filtered queue search performance.
2026-06-14 00:19:19 +02:00
Eric Allam 034058bce1 feat(webapp): add task metadata cache resolution metrics (#3934)
## Summary

Adds observability to the task metadata cache that backs the trigger hot
path. Follow-up to #3930, which made locked-version triggers fall back
to the primary when the read replica returns no row; this makes the
cache's effectiveness (and that fallback) measurable instead of
inferred.

## What it emits

A single bounded counter `task_meta_cache.resolve`, labeled by lookup
path (`locked` / `current`) and the source that satisfied it (`cache` /
`replica` / `writer` / `miss`):

- `cache / total` is the cache hit rate (its inverse is how cold the
cache runs).
- `writer / total` is how often the read replica returned empty for a
row the primary had (the condition #3930 recovers from).

Labels are bounded, with no per-env / worker / slug cardinality.

TRI-10873
2026-06-12 18:50:28 +01:00
Saadi Myftija 8b405711ac feat(supervisor): workload create duration histogram with backend and outcome labels (#3928)
Adds a `workload_create_duration_seconds` Prometheus histogram to the
supervisor, observed around the workload manager `create()` call:

- `backend` label: `kubernetes` | `compute` | `docker` — set once from
the configured workload manager
- `outcome` label: `success` | `error` — the per-outcome counts double
as a create error rate

Registered on the supervisor's existing metrics registry, so it's
exposed on the existing `/metrics` endpoint with no config changes.

Notes:
- Covers cold creates only; warm starts and restores return before
reaching `create()`.
- A create may include backend-internal retries, so one observation can
span multiple attempts.
- Fixed low cardinality: 2 active label sets per deployment × 10
buckets.
2026-06-12 18:38:04 +02:00
Eric Allam 52320679ab fix(webapp): stop locked-version triggers failing on stale replica reads (#3930)
## Summary

`triggerAndWait` (and other locked-version triggers) could
intermittently fail with `Task '<id>' not found on locked version
'<version>'` for a task that was registered on that version. The
failures came in bursts and recovered on their own, so a retry minutes
later would succeed.

## Root cause

For a locked-version trigger, the queue resolver looks up the task's
`BackgroundWorkerTask` metadata from the read replica (behind a Redis
cache). On a cache miss it queried the replica, and a `null` result was
treated as "task not registered" and turned into a non-retryable 422. A
read replica can return an empty result for a row that already exists on
the primary, so a momentarily-behind replica produced a false negative
even though the locked worker (resolved on the primary in the same
request) clearly had the task.

## Fix

On a cache miss, when the replica returns no row the resolver now
re-checks the primary before concluding the task is missing. If the
primary has the row it is used (and the cache is back-filled); the error
fires only when the primary genuinely lacks it, which is the only case
where the 422 is correct. The extra read happens on the
cache-miss-and-replica-empty path only, so the hot path is unchanged.

Verified with a unit test (replica stub vs. real primary) and end-to-end
against a local streaming replica with replication paused to reproduce
the stale read.

TRI-10868
2026-06-12 18:29:30 +02:00
Eric Allam 3d5cffc255 fix(cli): point to init when dev or update runs without a project (#3929)
## Summary

Running `trigger.dev dev` before setting up a project crashed with a raw
`Cannot find matching package.json` stack trace from a transitive
dependency, instead of telling the user what to do next. It happens
whenever `dev` (or `update`) runs in a directory with no `package.json`
in it or any parent directory, for example right after creating an empty
project folder, or when `init` was exited before it scaffolded anything.

The CLI now detects the missing project and prints actionable guidance
pointing at `init`.

## Fix

`dev` runs an embedded package-version check before it loads any project
config. That check resolved `package.json` through a helper that throws
when nothing is found up the tree, and nothing caught it. It is now
wrapped, so a missing `package.json` produces a clear "run init" message
and a clean exit.

The config loader had the same latent crash on the `--skip-update-check`
path. Its resolvers for `package.json`, the lockfile, and the workspace
root all ran before the friendly "couldn't find your trigger.config.ts"
check, so any of them throwing masked it. That check now runs first and
short-circuits before the resolvers touch the filesystem.

Verified live: in an empty directory, `dev`, `dev --skip-update-check`,
and `update` all print a "run init" message and exit cleanly; in a
configured project, `dev` still resolves config and boots normally.
2026-06-12 16:26:13 +00:00
Eric Allam 43b493628c docs(ai-chat): add the 4.5.0-rc.6 changelog entry (#3927)
## Summary

Adds the 4.5.0-rc.6 entry to the AI chat changelog, covering the
chat-facing items shipping in
[#3870](https://github.com/triggerdotdev/trigger.dev/pull/3870): the
chat.agent reliability batch, the continuation boot latency fix, the
chat.headStart hydration and reasoning fixes, the chat.createSession
stop and continuation fixes, and the new trigger skills installer.

Should merge alongside the release so the changelog matches the
published version.
2026-06-12 17:09:49 +01:00
Eric Allam 3bc3a1796f docs(ai-chat): custom agents page, backend decision table, and a building-agents anatomy entry (#3921)
## Summary

Documents the two lower-level chat backend APIs and restructures the
Building agents section so it has a sane reading order.

**Custom agents page.** `chat.customAgent()` was effectively
undocumented (one passing mention) and `chat.createSession()` was buried
at the bottom of the Backend page, prompted by a customer asking whether
dropping down a level was supported at all. Both now live on one
dedicated page framed as a composition: register with `customAgent`,
then drive turns with the managed `createSession` iterator or a
hand-rolled primitives loop. The page covers the patterns the managed
lifecycle otherwise handles for you, each verified against a running
agent: seeding history on continuation runs (and why the seed must go
through the turn-0 `addIncoming`, which replaces the accumulator),
persisting the user message before streaming so a mid-stream reload
keeps it, racing `totalUsage` after a stop so the loop cannot wedge, and
the single-message wire shape.

**Backend page.** Now leads with a decision table across the three
abstraction levels and focuses on `chat.agent()`, routing to the new
page. Stale examples that read a plural `messages` field off the wire
payload are fixed (copy-pasting them broke turn accumulation), and the
ChatSessionOptions / ChatTurn reference tables gain their missing rows
(`compaction`, `pendingMessages`, usage fields, `setMessages`,
`prepareStep`).

**Anatomy page + reorder.** The Building agents group opened with the
long How it works mechanics page, a wall right after the Quick Start. A
short Anatomy page now leads the group: the three moving parts, one
annotated example where each region names the page that covers it, and a
routing table. How it works moves to the end of the group as the depth
payoff, matching where peer docs put their internals pages.

All pages visually verified against a local Mintlify build; cross-links
and anchors updated across the section.
2026-06-12 17:09:37 +01:00
Eric Allam 84809b02ca docs(ai-chat): head-start persistence contract and a clearer sessions page (#3908)
## Summary

Two documentation improvements for the AI chat docs.

**Head-start persistence contract.** The fast starts page now documents
what your hooks can rely on across a head-start handover: one stable
assistant `messageId` for the whole turn, `onTurnComplete` as the
canonical persistence point, reasoning parts flowing into durable
history, and how Head Start composes with `hydrateMessages` (the
first-turn history arrives as `incomingMessages`, and the runtime
splices the warm partial onto the hydrated chain, deduplicated by id).
The hydrate examples on the lifecycle hooks and database persistence
pages now upsert their conversation row, since head-start first turns
run without a preload to create it.

**Sessions page.** The page opened with "a durable, task-bound,
bi-directional I/O channel pair", which reads as jargon and omitted run
orchestration entirely. It now leads with the plain mental model (a pair
of durable streams: input carries user messages, output carries
everything the agent produces) plus the Session's role orchestrating
runs, a diagram, a minimal runnable example, and a section on the
one-session-many-runs lifecycle.

Documents behavior shipping in
[#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907).
2026-06-12 17:08:49 +01:00
Eric Allam 51af9ae14c docs(ai-chat): correct chat.agent reference drift (#3892)
## Summary

Accuracy fixes across the AI chat docs: drop the non-existent per-call
option from `transport.preload`, clarify that `onValidateMessages` only
fires on turns carrying incoming messages, soften the turn-complete
token-refresh wording (the header is optional), document the new
`onTurnComplete` `error` field and `finishReason`, and correct the
idle-timeout default to 30 seconds.
2026-06-12 17:08:36 +01:00
Eric Allam b8a576a348 docs: document the trigger skills installer (replaces agent rules) (#3871)
## Summary

Updates the AI-tooling docs for the new `trigger skills` installer that
shipped in #3868. The Skills page now documents `trigger skills` (skills
bundled with the CLI, version-matched to your SDK) and the four bundled
skills: `authoring-tasks`, `realtime-and-frontend`,
`authoring-chat-agent`, `chat-agent-advanced`. The old Agent Rules page
becomes a short "rules are now skills" redirect (kept because existing
redirects and the CLI link point at it), and the Building with AI
overview collapses the three-way Skills/Rules/MCP comparison into Skills
vs MCP.

Hold until the v4.5 CLI release ships, since `trigger skills` is not on
npm until then.
2026-06-12 17:08:20 +01:00
Eric Allam 97c12e2510 docs(management): document TriggerClient for multi-target SDK usage (#3694)
## Summary

Docs follow-up for #3683 (`TriggerClient` for per-instance SDK
configuration). Adds a dedicated reference page and threads the new
pattern through the existing management + preview-branches docs.

## What's in

**New page** `docs/management/multiple-clients.mdx` — when to use
`TriggerClient` vs `configure()` vs `auth.withAuth`, env-var fallback
rules, isolation contract, namespace surface, `inheritContext` opt-in,
and a when-to-use-what table.

**Updated pages**

- `docs/management/authentication.mdx` — rewrote the `auth.withAuth`
section to reflect the now-ALS-backed semantics (the prior version
warned about concurrency races and pointed at issue #3298 as a tracked
fix; that fix landed in #3683). Added `tr_preview_*` to the key prefix
list. Reframed the multi-target use case to lead with `TriggerClient`,
with `auth.withAuth` as the temporary-override helper.
- `docs/management/overview.mdx` — added a `Multiple clients in one
process` subsection.
- `docs/deployment/preview-branches.mdx` — added a `Triggering across
multiple branches from one process` example.
- `docs/triggering.mdx` — one-liner pointing at the new page for
cross-project triggering.
- `docs/docs.json` — slotted `management/multiple-clients` into the
Management API nav, right after authentication.

Paired with #3683.

## Test plan

- [ ] Mintlify preview renders cleanly
- [ ] Code samples in each updated page run as documented
- [ ] Cross-page links resolve (`/management/multiple-clients`,
`/management/authentication`)
2026-06-12 17:08:04 +01:00
github-actions[bot] 5fab8cafcf chore: release v4.5.0-rc.6 (#3870)
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 5s
🚀 Publish Trigger.dev Docker / publish-worker-v4 (push) Has been skipped
🚀 Publish Trigger.dev Docker / scan-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-webapp (push) Has been skipped
🚀 Publish Trigger.dev Docker / publish-worker (push) Has been skipped
🧭 Helm Chart Release / lint-and-test (push) Has been cancelled
🧭 Helm Chart Release / release (push) Has been cancelled
🚀 Publish Trigger.dev Docker / 📣 Dispatch main image (push) Has been cancelled
## Summary
7 improvements, 1 bug fix.

## Improvements
- `trigger init` now sets up your AI coding assistant as part of project
setup: pick the MCP server, the agent skills, or both, then scaffold
with the CLI or hand off to your assistant. Adds a new `getting-started`
agent skill that teaches assistants how to bootstrap Trigger.dev
(install the SDK, write `trigger.config.ts`, create a first task, run
`trigger dev`), so the AI-driven setup path works end to end. It ships
in the CLI alongside the existing skills, version-matched to your SDK.
([#3872](https://github.com/triggerdotdev/trigger.dev/pull/3872))
- `dev` and `deploy` now fail with a clear error when two tasks are
defined with the same id, including across different task types (e.g. a
scheduled task and a regular task sharing an id). Previously the second
definition silently overwrote the first, so one of the tasks would
vanish with no warning. Task ids are detected as duplicates during
indexing (naming each offending id and the files it was found in), and
the same rule is enforced server-side when the background worker is
registered.
([#3865](https://github.com/triggerdotdev/trigger.dev/pull/3865))
- `trigger skills` installs Trigger.dev agent skills into your coding
agent so it knows how to write tasks, schedules, realtime, and
chat.agent code. The skills ship with the CLI and are copied into each
tool's native skills directory (Claude Code, Cursor, GitHub Copilot, and
Codex / AGENTS.md), and `trigger dev` offers to install them on first
run. ([#3868](https://github.com/triggerdotdev/trigger.dev/pull/3868))
- Reliability fixes for `chat.agent`. A user message sent while the
agent is streaming is no longer delivered twice (which could run a
duplicate turn), input appends now carry an idempotency key so a retried
send can't duplicate a message, stopping a generation clears the
streaming state so a page reload doesn't replay the stopped turn, and
runs can now carry the full set of dashboard tags instead of being
silently truncated. `onTurnComplete` now fires on errored turns (with
the thrown error attached) and the failed turn's user message is
persisted so it isn't lost on the next run. Custom agents and manual
`chat.writeTurnComplete` callers now trim the output stream, sending a
custom action no longer leaves a second stream reader running, and a
long-lived `watch` subscription no longer grows its dedupe set without
bound. ([#3891](https://github.com/triggerdotdev/trigger.dev/pull/3891))
- Continuation chat boots no longer stall for around 10 seconds before
the first turn. The `session.in` resume cursor is now found with a
non-blocking records read instead of draining an SSE long-poll (which
always waited out its full 5 second inactivity window, twice per boot),
the boot reads run concurrently, and chat snapshots carry the cursor so
subsequent boots skip the scan entirely.
([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907))
- Record client-side dequeue API latency in the supervisor consumer pool
as a Prometheus histogram
(`queue_consumer_pool_dequeue_duration_seconds`, labelled by `outcome`:
success/empty/error).
([#3887](https://github.com/triggerdotdev/trigger.dev/pull/3887))
- Add `GetProjectEnvironmentsResponseBody` and `ProjectEnvironment`
schemas for the new `GET /api/v1/projects/{projectRef}/environments`
endpoint, which lists the parent environments (dev, staging, preview,
prod) a personal access token can access for a project. Dev is scoped to
the token owner and branch (preview child) environments are excluded.
([#3880](https://github.com/triggerdotdev/trigger.dev/pull/3880))

## Bug fixes
- Fix two `chat.createSession()` bugs: stopping a generation no longer
wedges the run (the turn loop raced a `totalUsage` promise that never
settles after a stop-abort), and continuation runs now wait for the next
message instead of invoking the model with an empty prompt.
([#3920](https://github.com/triggerdotdev/trigger.dev/pull/3920))

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

⚠️⚠️⚠️⚠️⚠️⚠️

`main` is currently in **pre mode** so this branch has prereleases
rather than normal releases. If you want to exit prereleases, run
`changeset pre exit` on `main`.

⚠️⚠️⚠️⚠️⚠️⚠️

# Releases
## @trigger.dev/build@4.5.0-rc.6

### Patch Changes

-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`

## trigger.dev@4.5.0-rc.6

### Patch Changes

- `trigger init` now sets up your AI coding assistant as part of project
setup: pick the MCP server, the agent skills, or both, then scaffold
with the CLI or hand off to your assistant. Adds a new `getting-started`
agent skill that teaches assistants how to bootstrap Trigger.dev
(install the SDK, write `trigger.config.ts`, create a first task, run
`trigger dev`), so the AI-driven setup path works end to end. It ships
in the CLI alongside the existing skills, version-matched to your SDK.
([#3872](https://github.com/triggerdotdev/trigger.dev/pull/3872))

- `dev` and `deploy` now fail with a clear error when two tasks are
defined with the same id, including across different task types (e.g. a
scheduled task and a regular task sharing an id). Previously the second
definition silently overwrote the first, so one of the tasks would
vanish with no warning. Task ids are detected as duplicates during
indexing (naming each offending id and the files it was found in), and
the same rule is enforced server-side when the background worker is
registered.
([#3865](https://github.com/triggerdotdev/trigger.dev/pull/3865))

- `trigger skills` installs Trigger.dev agent skills into your coding
agent so it knows how to write tasks, schedules, realtime, and
chat.agent code. The skills ship with the CLI and are copied into each
tool's native skills directory (Claude Code, Cursor, GitHub Copilot, and
Codex / AGENTS.md), and `trigger dev` offers to install them on first
run. ([#3868](https://github.com/triggerdotdev/trigger.dev/pull/3868))

    ```bash
    trigger skills --target claude-code
    ```

Replaces the previous `install-rules` command, which stays as an alias.

-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`
    -   `@trigger.dev/build@4.5.0-rc.6`
    -   `@trigger.dev/schema-to-json@4.5.0-rc.6`

## @trigger.dev/core@4.5.0-rc.6

### Patch Changes

- Reliability fixes for `chat.agent`. A user message sent while the
agent is streaming is no longer delivered twice (which could run a
duplicate turn), input appends now carry an idempotency key so a retried
send can't duplicate a message, stopping a generation clears the
streaming state so a page reload doesn't replay the stopped turn, and
runs can now carry the full set of dashboard tags instead of being
silently truncated. `onTurnComplete` now fires on errored turns (with
the thrown error attached) and the failed turn's user message is
persisted so it isn't lost on the next run. Custom agents and manual
`chat.writeTurnComplete` callers now trim the output stream, sending a
custom action no longer leaves a second stream reader running, and a
long-lived `watch` subscription no longer grows its dedupe set without
bound. ([#3891](https://github.com/triggerdotdev/trigger.dev/pull/3891))
- Continuation chat boots no longer stall for around 10 seconds before
the first turn. The `session.in` resume cursor is now found with a
non-blocking records read instead of draining an SSE long-poll (which
always waited out its full 5 second inactivity window, twice per boot),
the boot reads run concurrently, and chat snapshots carry the cursor so
subsequent boots skip the scan entirely.
([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907))
- Record client-side dequeue API latency in the supervisor consumer pool
as a Prometheus histogram
(`queue_consumer_pool_dequeue_duration_seconds`, labelled by `outcome`:
success/empty/error).
([#3887](https://github.com/triggerdotdev/trigger.dev/pull/3887))
- `dev` and `deploy` now fail with a clear error when two tasks are
defined with the same id, including across different task types (e.g. a
scheduled task and a regular task sharing an id). Previously the second
definition silently overwrote the first, so one of the tasks would
vanish with no warning. Task ids are detected as duplicates during
indexing (naming each offending id and the files it was found in), and
the same rule is enforced server-side when the background worker is
registered.
([#3865](https://github.com/triggerdotdev/trigger.dev/pull/3865))
- Add `GetProjectEnvironmentsResponseBody` and `ProjectEnvironment`
schemas for the new `GET /api/v1/projects/{projectRef}/environments`
endpoint, which lists the parent environments (dev, staging, preview,
prod) a personal access token can access for a project. Dev is scoped to
the token owner and branch (preview child) environments are excluded.
([#3880](https://github.com/triggerdotdev/trigger.dev/pull/3880))

## @trigger.dev/python@4.5.0-rc.6

### Patch Changes

-   Updated dependencies:
    -   `@trigger.dev/sdk@4.5.0-rc.6`
    -   `@trigger.dev/core@4.5.0-rc.6`
    -   `@trigger.dev/build@4.5.0-rc.6`

## @trigger.dev/react-hooks@4.5.0-rc.6

### Patch Changes

-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`

## @trigger.dev/redis-worker@4.5.0-rc.6

### Patch Changes

-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`

## @trigger.dev/rsc@4.5.0-rc.6

### Patch Changes

-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`

## @trigger.dev/schema-to-json@4.5.0-rc.6

### Patch Changes

-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`

## @trigger.dev/sdk@4.5.0-rc.6

### Patch Changes

- Reliability fixes for `chat.agent`. A user message sent while the
agent is streaming is no longer delivered twice (which could run a
duplicate turn), input appends now carry an idempotency key so a retried
send can't duplicate a message, stopping a generation clears the
streaming state so a page reload doesn't replay the stopped turn, and
runs can now carry the full set of dashboard tags instead of being
silently truncated. `onTurnComplete` now fires on errored turns (with
the thrown error attached) and the failed turn's user message is
persisted so it isn't lost on the next run. Custom agents and manual
`chat.writeTurnComplete` callers now trim the output stream, sending a
custom action no longer leaves a second stream reader running, and a
long-lived `watch` subscription no longer grows its dedupe set without
bound. ([#3891](https://github.com/triggerdotdev/trigger.dev/pull/3891))
- Continuation chat boots no longer stall for around 10 seconds before
the first turn. The `session.in` resume cursor is now found with a
non-blocking records read instead of draining an SSE long-poll (which
always waited out its full 5 second inactivity window, twice per boot),
the boot reads run concurrently, and chat snapshots carry the cursor so
subsequent boots skip the scan entirely.
([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907))
- Fix `chat.headStart` when `hydrateMessages` is registered. The warm
route's step-1 partial now reaches the agent's accumulator on the
hydrate path, so `onTurnComplete` carries the full first turn (the
head-start user message included), tool-call handovers resume from step
2 instead of re-running step 1, and the assistant `messageId` stays
stable across the handover.
([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907))
- Preserve reasoning parts across the `chat.headStart` handover.
Extended-thinking models' step-1 reasoning now lands in the durable
session history (and `onTurnComplete`) under the same assistant
`messageId`, with provider metadata intact so Anthropic thinking
signatures survive replays.
([#3907](https://github.com/triggerdotdev/trigger.dev/pull/3907))
- Fix two `chat.createSession()` bugs: stopping a generation no longer
wedges the run (the turn loop raced a `totalUsage` promise that never
settles after a stop-abort), and continuation runs now wait for the next
message instead of invoking the model with an empty prompt.
([#3920](https://github.com/triggerdotdev/trigger.dev/pull/3920))
-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`

## @trigger.dev/plugins@4.5.0-rc.6

### Patch Changes

-   Updated dependencies:
    -   `@trigger.dev/core@4.5.0-rc.6`

</details>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
helm-v4.5.0-rc.6 v.docker.4.5.0-rc.6 v4.5.0-rc.6
2026-06-12 16:47:00 +01:00
Iss 002c441f50 feat(webapp): self serve schedules add-on (#3811)
Adds the purchase UI for extra schedules, mirroring preview branches

## Changes
- `setSchedulesAddOn` platform client + `SetSchedulesAddOnService`
(purchase + quota-increase via Plain).
- `ScheduleListPresenter` surfaces add-on / quota / pricing;
`checkSchedule` counts purchased schedules toward the limit (`base +
purchased`).
- `PurchaseSchedulesModal` on the Schedules page — bought in **bundles
of 1,000 ($10/mo each)**; bundle increments enforced client-side and in
the action's zod schema.
2026-06-12 11:27:06 -04:00
Eric Allam 47834198fc fix(sdk): stop chat.createSession wedging on stop and erroring on continuation boots (#3920)
## Summary

Two `chat.createSession()` bugs that break chats at its abstraction
level:

1. **Stopping a generation wedged the run forever.** `turn.complete()`
bare-awaited the AI SDK's `totalUsage` promise, which never settles
after a stop-abort. The run stayed stuck inside the stopped turn (trace
shows a permanently partial `ai.streamText` span and no further `waiting
for next message`), so the chat could never take another message. Fixed
with the same 2s `Promise.race` guard `chat.agent`'s turn loop already
uses.

2. **Continuation runs invoked the model with an empty prompt.** The
first turn only waited for a message on `preload` boots. A continuation
run (spawned after a cancel, crash, or version upgrade) arrives with the
boot payload stripped, so the loop ran a turn with zero messages and
errored with `AI_InvalidPromptError: messages must not be empty`.
Message-less continuation boots now wait for the next session input
("waiting for first message (continuation)"), and `turn.continuation` is
preserved across the wait so user code can seed stored history off it.

Both reproduced and verified end-to-end against a live environment (stop
followed by a next turn; cancel followed by a continuation turn with
seeded history), plus the existing unit suite.
2026-06-12 14:07:45 +01:00
Eric Allam a04cdffda6 fix(webapp): stop replica lag from double-triggering session runs and 404ing fresh sessions (#3914)
## Summary

Two read-replica races on the session APIs could break chats whose first
activity lands inside the replication window (or any time the replica
lags):

1. A session's first `.in` append or `.out` subscribe could fail with a
404 for a session that exists on the writer, because the route resolved
the Session row on the replica only.
2. `ensureRunForSession` probed run liveness on the replica, so a probe
miss on a run triggered moments earlier was judged "run is dead" and a
second live run was spawned for the same session. Both runs then
consumed the same input stream, producing duplicated turns and doubled
responses (and doubled LLM cost).

## Fix

Liveness now re-probes the writer before declaring the current run dead
(the old code already fell back to the writer, but only to recover the
friendlyId, after the wrong verdict was made). Session resolution on the
append and subscribe/init routes goes through a new
`resolveSessionWithWriterFallback`, which stays replica-first on the hot
path and only touches the writer on a miss.

Reproduced and verified against a local streaming replica with an
artificial apply delay: pre-fix, a send immediately after session
creation reliably produced either the 404 or two executing runs with a
doubled response; post-fix, the same flow produces exactly one run and
one response.

Also rides along: the local docker replica's default apply delay drops
from 150ms to a realistic 20ms (override via `REPLICA_APPLY_DELAY` when
you want to deliberately widen the race window).
2026-06-12 14:07:36 +01:00
Matt Aitken eb498d137f fix(plugins): drop unused gitBranch re-export from the package entry (#3923)
`@trigger.dev/plugins` re-exported
`sanitizeBranchName`/`isValidGitBranchName` from `@trigger.dev/core` as
a convenience forwarder. Nothing actually imports them through this
package — every consumer (webapp, `@trigger.dev/rbac`, …) imports them
directly from `@trigger.dev/core/v3/utils/gitBranch`.

Removing the forwarder keeps the package entry free of **runtime** core
imports (only type re-exports + `buildJwtAbility` remain), so consumers
that bundle `@trigger.dev/plugins` from source don't pull an unrelated
core subpath into their build.

No behavior change; the helpers remain available from
`@trigger.dev/core` where they're defined.
2026-06-12 12:16:04 +00:00
Matt Aitken f48c89752c perf(webapp): parallelize streaming batch-item ingest (#3777)
## Problem

The item-streaming endpoint of the two-phase batch API (`POST
/api/v3/batches/:batchId/items`) processed streamed items strictly
sequentially. For a batch of many large payloads, each offloaded to
object storage inline, this serialized N object-store round-trips inside
a single request and could exceed Node's default `server.requestTimeout`
(300s). The webapp then returned `408`, which the SDK reads as `408
terminated` and retries up to 5 times, turning a slow ingest into a
failure that takes tens of minutes to surface.

## Fix

Ingest now runs through `p-map` over the NDJSON async iterable with
bounded concurrency (`STREAMING_BATCH_INGEST_CONCURRENCY`, default 10):

- `p-map` pulls lazily from the stream, so at most `concurrency` items
are read and in-flight at once. Peak memory stays bounded to roughly
`concurrency × STREAMING_BATCH_ITEM_MAXIMUM_SIZE` and request-body
backpressure is preserved.
- Set the env to `1` for fully sequential ingestion (escape hatch).

## Why this is safe (ordering and idempotency unchanged)

- Ordering derives from each item's index (enqueue `timestamp =
batch.createdAt + index`), not enqueue order.
- Dedup is atomic per index in `enqueueBatchItem`.
- The NDJSON parser now stamps oversized-item markers with their emit
position, removing the consumer's sequential `lastIndex` assumption (the
only order-dependent bit).
- The count-check and conditional-seal path is untouched.

## Scope

This speeds up every batch ingested through the streaming endpoint, not
just large-payload batches. Each item does a per-item Redis enqueue
regardless of size, and those now overlap. Large payloads benefit most
because they add an object-store offload round-trip on top of the
enqueue.

## Verification

Added an integration test (`streamBatchItems.test.ts`) that drives the
real service against Postgres + Redis + RunEngine and times a 150-item
batch at increasing concurrency. Object-store offload is modelled as a
fixed per-item latency (local round-trips are too small to compare
meaningfully):

```
runCount=150
  large payloads (10ms/item offload):
    concurrency=1   1739ms
    concurrency=10  192ms  (9.1x faster)
    concurrency=50  57ms   (30.7x faster)
  small payloads (Redis enqueue only, no offload):
    concurrency=1   90ms
    concurrency=10  24ms   (3.7x faster)
```

The test asserts correctness at every concurrency (all items accepted,
sealed, enqueued exactly once), that parallel ingest beats the
sequential floor, and that the small-payload case is strictly faster
than sequential, so the win is not specific to large payloads.

Also exercised end-to-end over real HTTP against a local server: a
20-item batch (12MB body) ingests and seals, a re-stream of the sealed
batch returns `sealed: true` with zero re-accepted items (idempotent
retry), and an oversized item still seals at its correct index.

Existing coverage stays green: concurrent ingest of a 100-item batch,
in-flight processing never exceeding the configured concurrency,
concurrent dedup on streaming retry, and emit-position marker indexing.

## Follow-ups (not in this PR)

- SDK pre-offload of large item payloads (send `application/store` refs
instead of raw blobs) to remove object-store work from the request hot
path and shrink the request body.
- Optional `server.requestTimeout` bump as a safety net.

## CI fix

Added `.github/workflows/codeql.yml` to replace GitHub's automatic
("dynamic") CodeQL scanning. The dynamic setup was failing to upload
SARIF results because the auto-generated `GITHUB_TOKEN` lacked the
`security-events: write` permission. The explicit workflow grants that
permission at the job level and pins all actions to commit SHAs,
consistent with the repo's security conventions.

##  Checklist

- [ ] I have followed every step in the [contributing
guide](https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md)
- [ ] The PR title follows the convention.
- [ ] I ran and tested the code works

---

## Testing

- Integration test (`streamBatchItems.test.ts`) validates correctness
and performance at concurrency 1, 10, and 50 for both large and small
payloads.
- End-to-end verified over real HTTP: 20-item/12MB batch ingests and
seals, idempotent retry returns `sealed: true`, oversized item seals at
correct index.

---

## Changelog

Streaming batch ingest now processes items with bounded concurrency
instead of one at a time, so batches of many large payloads ingest far
faster and no longer time out. Concurrency is configurable via
`STREAMING_BATCH_INGEST_CONCURRENCY` (default 10); set it to 1 for fully
sequential ingestion.

---

## Screenshots

_[Screenshots]_

💯

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 13:01:29 +01:00
Matt Aitken 5d6ea33166 refactor: share the public-token JWT scope decoder; make @trigger.dev/plugins internal (#3919)
## What

`buildJwtAbility` — the decoder for public-token scope strings
(`read:tags:…`, `read:runs:run_abc`, `admin`, …) — now lives in
`@trigger.dev/plugins` as the single source of truth.
`@trigger.dev/rbac` re-exports it, so the built-in fallback and any auth
plugin interpret a token identically.

Scope strings are split on only the first **two** colons
(`action:type:id`), so a resource id that itself contains colons — e.g.
a tag like `user:123` — is matched in full rather than truncated to its
first segment. (The fallback already did this; this makes it the one
shared implementation.)

`@trigger.dev/plugins` is now **private (unpublished)** and gains a
`@triggerdotdev/source` export condition, so consumers bundle it from
source per-commit like `@trigger.dev/core` instead of resolving a
published version — no cross-version coordination.

## Why

Two hand-maintained copies of the scope grammar drift, and the
difference silently changes what a token grants. One shared decoder
removes that class of bug.

## Notes

- No changeset: `@trigger.dev/plugins` is now private and
`@trigger.dev/rbac` is internal — neither is published.
- Unit coverage for the colon-id path lives in
`internal-packages/rbac/src/ability.test.ts` (now exercising the shared
function).
2026-06-12 12:44:32 +01:00
Eric Allam 78b7136bf7 chore: vouch saasjesus as a contributor (#3917)
Vouches `saasjesus` as a contributor (vouch request #3915) so their PRs
clear the vouch check instead of being auto-closed.
2026-06-12 11:56:45 +01:00
nicktrn de8231cb9d chore: bump shell-quote to 1.8.4 (#3913)
Refreshes the locked `shell-quote` to 1.8.4 (transitive, lockfile-only).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/triggerdotdev/trigger.dev/pull/3913?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-12 09:42:35 +00:00
Eric Allam 954ee5c572 fix(webapp): deliver realtime changes with current content when the read replica lags (#3910)
## Summary

When the realtime runs feed (the backend behind the `realtimeBackend`
feature flag) hydrates a change from a Postgres read replica, the read
can race the replica's apply of the very write that triggered it. The
delivered row then carries the previous change's content, and an
isolated final change (for example a last `metadata.set` before a run
goes quiet) is not corrected until the roughly 20 second backstop poll.
Measured against a replica with deliberate apply delay, every delivery
trailed exactly one change behind and a final change stranded for the
full backstop interval.

## Fix

Publishers stamp each change record with the committed row's
`updatedAt`, taken from writes they already perform, so the stamp costs
no extra queries. The router delays its wake hydrate until the replica's
measured lag has passed, anchored to that timestamp: a record that has
already spent longer than the lag in transit is hydrated immediately, so
only the racing leading edge ever waits. After hydrating, a tripwire
compares each row against its record's watermark. Still-stale rows are
withheld and retried briefly, and each detection feeds the lag estimate.
If retries run out, the rows are delivered anyway (liveness over
freshness) and follow-up re-hydrates emit the fresh version through the
normal working-set diff once the replica catches up, with the backstop
as the terminal net.

Replica lag is sampled reader-side only, and only while feeds are
active. Aurora reports live lag via `aurora_replica_status()`; vanilla
Postgres can only report "caught up or not" (mid-apply lag is not
honestly measurable from a replica), so tripwire observations floor the
estimate there. Deployments without a replica resolve to zero lag and
skip the gate entirely. Tunables live under
`REALTIME_BACKEND_NATIVE_REPLICA_LAG_*`, and
`realtime_native.stale_hydrates` plus
`realtime_native.replica_lag_estimate_ms` make replica health
observable.

Two adjacent fixes: a metadata update that writes nothing no longer
publishes a change record, and buffered parent and root metadata
operations now publish when the flusher writes them, so those changes
wake live feeds instead of waiting for the backstop.

For local testing, `docker-compose` gains an opt-in `database-replica`
service (compose profile `replica`) with a configurable
`recovery_min_apply_delay`, which reproduces replica-lag behavior
deterministically. With the gate disabled this rig reproduces the
one-change-behind delivery exactly; with it enabled, deliveries arrive
with current content at roughly the true replica lag, across write rates
faster and slower than the lag itself.
2026-06-12 07:34:50 +01:00