feat(webapp): allow version downgrades via promote API#3214
feat(webapp): allow version downgrades via promote API#3214chengzp wants to merge 2 commits intotriggerdotdev:mainfrom
Conversation
|
WalkthroughThis pull request introduces a feature to allow bypassing version validation when promoting or rolling back worker deployments. It adds a new Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/webapp/app/routes/api.v1.deployments`.$deploymentVersion.promote.ts:
- Around line 35-37: The code reads allowRollbacks from the URL without schema
validation; create a Zod QuerySchema (e.g., const QuerySchema = z.object({
allowRollbacks: z.optional(z.union([z.literal("true"), z.literal("false")])) }))
and parse url.searchParams via QuerySchema.parse or safeParse, then derive
allowRollbacks as parsed.allowRollbacks === "true"; on parse failure return a
400 response (consistent with other routes) and include the validation error
message. Update the usage of request/url and the allowRollbacks variable to use
the validated parsed result.
In `@apps/webapp/app/v3/services/changeCurrentDeployment.server.ts`:
- Around line 10-14: The current call method accepts disableVersionCheck and
uses it to skip ordering/version safety for both "promote" and "rollback";
change the logic so disableVersionCheck is only honored when direction ===
"promote" (i.e., only allow bypassing the version-order check for promotions),
and ensure rollback paths always perform the ordering/version checks regardless
of the disableVersionCheck flag; update any conditional(s) in the call method
that reference disableVersionCheck and the direction to explicitly require
direction === "promote" before skipping checks (use the deployment, direction,
and disableVersionCheck parameters to locate the code).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 54146e47-65d5-4b37-8575-2cf689514197
📒 Files selected for processing (3)
.server-changes/allow-rollbacks-promote-api.mdapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.tsapps/webapp/app/v3/services/changeCurrentDeployment.server.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (15)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead
**/*.{ts,tsx}: Use task export syntax: export const myTask = task({ id: 'my-task', run: async (payload) => { ... } })
Use Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work - avoid DEPRECATED zodworker (Graphile-worker wrapper)
Prisma 6.14.0 client and schema use PostgreSQL in internal-packages/database - import only from Prisma client
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use zod for validation in packages/core and apps/webapp
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
apps/webapp/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Access all environment variables through the
envexport ofenv.server.tsinstead of directly accessingprocess.envin the Trigger.dev webapp
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
apps/webapp/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
apps/webapp/**/*.{ts,tsx}: When importing from@trigger.dev/corein the webapp, use subpath exports from the package.json instead of importing from the root path
Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
apps/webapp/app/v3/services/**/*.server.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)
Organize services in the webapp following the pattern
app/v3/services/*/*.server.ts
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}
📄 CodeRabbit inference engine (AGENTS.md)
Format code using Prettier before committing
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
apps/webapp/**/*.server.ts
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Access environment variables via the
envexport fromapp/env.server.ts, never useprocess.envdirectly
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
apps/webapp/app/v3/services/**/*.server.ts
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
When editing services that branch on
RunEngineVersionto support both V1 and V2 (e.g.,cancelTaskRun.server.ts,batchTriggerV3.server.ts), only modify V2 code paths
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
apps/{webapp,supervisor}/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
When modifying only server components (apps/webapp/, apps/supervisor/) with no package changes, add a .server-changes/ file instead of a changeset
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx,js}: Always import from@trigger.dev/sdkfor Trigger.dev tasks - never use@trigger.dev/sdk/v3or deprecated client.defineJob
Import subpaths only from@trigger.dev/core, never import from root
Add crumbs as you write code using //@crumbscomments or //#region@crumbsblocks for agentcrumbs debug tracing
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
apps/webapp/app/v3/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
When modifying V3 code paths in apps/webapp/app/v3/, only modify V2 code - consult apps/webapp/CLAUDE.md for V1-only legacy code to avoid
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
apps/webapp/**/*.{ts,tsx,jsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
Remix 2.1.0 is used in apps/webapp for the main API, dashboard, and orchestration with Express server
Files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
apps/webapp/app/routes/**/*.ts
📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)
Use Remix flat-file route convention with dot-separated segments (e.g.,
api.v1.tasks.$taskId.trigger.tsfor/api/v1/tasks/:taskId/trigger)
Files:
apps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/src/commands/promote.ts : Implement `promote.ts` command in `src/commands/` for deployment promotion
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/src/commands/promote.ts : Implement `promote.ts` command in `src/commands/` for deployment promotion
Applied to files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
📚 Learning: 2026-03-02T12:42:56.114Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.ts : When editing services that branch on `RunEngineVersion` to support both V1 and V2 (e.g., `cancelTaskRun.server.ts`, `batchTriggerV3.server.ts`), only modify V2 code paths
Applied to files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
📚 Learning: 2026-03-13T13:37:49.544Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.544Z
Learning: Applies to apps/webapp/app/v3/**/*.{ts,tsx} : When modifying V3 code paths in apps/webapp/app/v3/, only modify V2 code - consult apps/webapp/CLAUDE.md for V1-only legacy code to avoid
Applied to files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/src/commands/deploy.ts : Implement `deploy.ts` command in `src/commands/` for production deployment
Applied to files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.tsapps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts
📚 Learning: 2026-03-13T13:37:49.544Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-13T13:37:49.544Z
Learning: Applies to {packages,integrations}/**/*.{ts,tsx,js,json} : When modifying public packages (packages/* or integrations/*), add a changeset using pnpm run changeset:add with default patch level for bug fixes and minor changes
Applied to files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
📚 Learning: 2026-03-10T17:56:20.938Z
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 3201
File: apps/webapp/app/v3/services/setSeatsAddOn.server.ts:25-29
Timestamp: 2026-03-10T17:56:20.938Z
Learning: Do not implement local userId-to-organizationId authorization checks inside org-scoped service classes (e.g., SetSeatsAddOnService, SetBranchesAddOnService) in the web app. Rely on route-layer authentication (requireUserId(request)) and org membership enforcement via the _app.orgs.$organizationSlug layout route. Any userId/organizationId that reaches these services from org-scoped routes has already been validated. Apply this pattern across all org-scoped services to avoid redundant auth checks and maintain consistency.
Applied to files:
apps/webapp/app/v3/services/changeCurrentDeployment.server.ts
🔇 Additional comments (2)
.server-changes/allow-rollbacks-promote-api.md (1)
1-6: Changelog entry is clear and correctly scoped.This documents the API behavior change accurately and is ready to ship.
apps/webapp/app/routes/api.v1.deployments.$deploymentVersion.promote.ts (1)
53-53: Service wiring for rollback flag propagation looks good.Passing the parsed
allowRollbacksflag through to the service keeps the route behavior aligned with the new API capability.
| public async call( | ||
| deployment: WorkerDeployment, | ||
| direction: ChangeCurrentDeploymentDirection, | ||
| disableVersionCheck?: boolean | ||
| ) { |
There was a problem hiding this comment.
Limit version-check bypass to promotions only.
Line 49 currently disables both "promote" and "rollback" ordering checks. That makes rollback safety checks bypassable by any future caller that passes true, which is broader than the rollout goal (downgrades via promote API).
Suggested fix
- if (!disableVersionCheck) {
- switch (direction) {
- case "promote": {
- if (
- compareDeploymentVersions(
- currentPromotion.deployment.version,
- deployment.version
- ) >= 0
- ) {
- throw new ServiceValidationError(
- "Cannot promote a deployment that is older than the current deployment."
- );
- }
- break;
- }
- case "rollback": {
- if (
- compareDeploymentVersions(
- currentPromotion.deployment.version,
- deployment.version
- ) <= 0
- ) {
- throw new ServiceValidationError(
- "Cannot rollback to a deployment that is newer than the current deployment."
- );
- }
- break;
- }
- }
- }
+ switch (direction) {
+ case "promote": {
+ if (
+ !disableVersionCheck &&
+ compareDeploymentVersions(
+ currentPromotion.deployment.version,
+ deployment.version
+ ) >= 0
+ ) {
+ throw new ServiceValidationError(
+ "Cannot promote a deployment that is older than the current deployment."
+ );
+ }
+ break;
+ }
+ case "rollback": {
+ if (
+ compareDeploymentVersions(
+ currentPromotion.deployment.version,
+ deployment.version
+ ) <= 0
+ ) {
+ throw new ServiceValidationError(
+ "Cannot rollback to a deployment that is newer than the current deployment."
+ );
+ }
+ break;
+ }
+ }Also applies to: 49-76
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/webapp/app/v3/services/changeCurrentDeployment.server.ts` around lines
10 - 14, The current call method accepts disableVersionCheck and uses it to skip
ordering/version safety for both "promote" and "rollback"; change the logic so
disableVersionCheck is only honored when direction === "promote" (i.e., only
allow bypassing the version-order check for promotions), and ensure rollback
paths always perform the ordering/version checks regardless of the
disableVersionCheck flag; update any conditional(s) in the call method that
reference disableVersionCheck and the direction to explicitly require direction
=== "promote" before skipping checks (use the deployment, direction, and
disableVersionCheck parameters to locate the code).
There was a problem hiding this comment.
I feel like this is okay, given how isolated the change is. But happy to make this change too if reviewer feel strongly.
There was a problem hiding this comment.
The PR description image shows a deployment table with two entries — h7mvlm0n (version 20260314.2, Deployed) and pzaalcga (version 20260314.1, marked Current) — confirming the downgrade-via-promote scenario the PR is testing.
@chengzp That's a fair point — since disableVersionCheck is only ever passed as true from the promote API endpoint today, there's no immediate safety gap. My suggestion is purely defensive: scoping the bypass explicitly to direction === "promote" prevents a future caller from inadvertently skipping the rollback ordering check by reusing this flag. The rollback direction already semantically implies "going backward," so unconditionally enforcing its version check feels like a reasonable invariant to lock in now.
That said, I agree it's not a blocker given the current isolation. Happy to leave it as-is if the reviewer is comfortable — just flagging it as a low-cost guard for future maintainability.
🧠 Learnings used
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/src/commands/promote.ts : Implement `promote.ts` command in `src/commands/` for deployment promotion
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/src/commands/deploy.ts : Implement `deploy.ts` command in `src/commands/` for production deployment
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: apps/webapp/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:56.114Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.ts : When editing services that branch on `RunEngineVersion` to support both V1 and V2 (e.g., `cancelTaskRun.server.ts`, `batchTriggerV3.server.ts`), only modify V2 code paths
Learnt from: samejr
Repo: triggerdotdev/trigger.dev PR: 3201
File: apps/webapp/app/v3/services/setSeatsAddOn.server.ts:25-29
Timestamp: 2026-03-10T17:56:20.938Z
Learning: Do not implement local userId-to-organizationId authorization checks inside org-scoped service classes (e.g., SetSeatsAddOnService, SetBranchesAddOnService) in the web app. Rely on route-layer authentication (requireUserId(request)) and org membership enforcement via the _app.orgs.$organizationSlug layout route. Any userId/organizationId that reaches these services from org-scoped routes has already been validated. Apply this pattern across all org-scoped services to avoid redundant auth checks and maintain consistency.
✅ Checklist
Testing
Deployed twice with
pnpm exec trigger deployChangelog
Adds an
allowRollbacksquery param to the promote deployment API endpoint (POST /api/v1/deployments/:version/promote?allowRollbacks=true). When set to true, the version ordering check inChangeCurrentDeploymentServiceis skipped, allowing older deployments to be promoted.Screenshots
N/A