0c839e85666a67b505569f1481b4fcf371b319ee
7413 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
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> |
||
|
|
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. |
||
|
|
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" /> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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 |
||
|
|
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).
|
||
|
|
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 ``` |
||
|
|
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 |
||
|
|
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>
|
||
|
|
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 |
||
|
|
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> |
||
|
|
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. |
||
|
|
ab3a1e593a | docs: use one canonical definition of a Session everywhere (#3956) | ||
|
|
39fca87b48 | docs: add troubleshooting entry for runs not dequeuing in dev (#3955) | ||
|
|
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. |
||
|
|
545ecf7beb | feat(plugins): add SSO plugin contract to @trigger.dev/plugins (#3949) | ||
|
|
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
|
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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`.
|
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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). |
||
|
|
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. |
||
|
|
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. |
||
|
|
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`) |
||
|
|
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 |
||
|
|
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. |
||
|
|
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.
|
||
|
|
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). |
||
|
|
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. |
||
|
|
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>
|
||
|
|
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). |
||
|
|
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. |
||
|
|
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. --> |
||
|
|
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. |