Skip to content

feat(webapp): deprecate v3 CLI deploys server-side#3415

Merged
ericallam merged 1 commit intomainfrom
devin/1776690995-deprecate-v3-cli-deploys
Apr 20, 2026
Merged

feat(webapp): deprecate v3 CLI deploys server-side#3415
ericallam merged 1 commit intomainfrom
devin/1776690995-deprecate-v3-cli-deploys

Conversation

@ericallam
Copy link
Copy Markdown
Member

✅ Checklist

  • I have followed every step in the contributing guide
  • The PR title follows the convention.
  • I ran and tested the code works

Summary

Adds a server-side gate that detects deploy attempts from v3 CLI versions (i.e. trigger.dev@3.x) at the POST /api/v1/deployments entry point and, when enabled, rejects them with a clear upgrade message. v4 CLI deploys are completely unaffected.

The last 3.x CLI release was 3.3.7, which we can't update. This approach short-circuits the deploy before any DB writes, image-ref generation, S2 stream creation, or queue enqueue — no side effects in either mode.

How v3 vs v4 are distinguished

I pulled the published CLI tarballs for trigger.dev@3.3.7, 4.0.0, 4.0.1, 4.0.5, 4.1.0, 4.2.0, and the current 4.4.4 in the repo. The cleanest, most reliable signal is the request body to POST /api/v1/deployments:

Field on initialize v3.3.7 CLI v4.x CLI
type never sent always sent — "MANAGED" (run_engine_v2) or "V1"
isNativeBuild / gitMeta / triggeredVia / runtime not sent sent
registryHost / namespace sent (v3-only; stripped by current Zod schema) not sent

Every v4 call site I inspected sets type: features.run_engine_v2 ? "MANAGED" : "V1" unconditionally. payload.type is undefined if and only if the client is a 3.x CLI.

Behavior

  • Detection always runs and emits logger.warn("Detected deploy from deprecated v3 CLI", { environmentId, projectId, organizationId, enforced }), which lets us watch how many v3 deploys are still happening before enforcement is flipped.

  • Enforcement is gated behind DEPRECATE_V3_CLI_DEPLOYS_ENABLED (default "0", off). When "1", the server returns 400 with:

    The trigger.dev CLI v3 is no longer supported for deployments. Please upgrade your project to v4: https://trigger.dev/docs/migrating-from-v3

    The v3 CLI surfaces this verbatim as Failed to start deployment: <message> because zodfetch throws ApiError for non-retryable 4xx (400/422) and deploy.js in 3.3.7 prints error.message.

Out of scope (intentionally)

  • api.v1.deployments.$deploymentId.finalize.ts / FinalizeDeploymentService / createDeploymentBackgroundWorkerV3.server.ts are V1-engine paths, not the v3 CLI gate. Leaving them alone per review.
  • Container-side createDeploymentBackgroundWorker call in managed-index-controller.ts is still used by v4's in-image indexer. Not touched.
  • v3 trigger dev flow (different code path) — separate deprecation if/when needed.

Testing

  • Ran pnpm run typecheck --filter webapp locally — passes.
  • Verified v4 tarballs (4.0.0, 4.0.1, 4.0.5, 4.1.0, 4.2.0, 4.4.4) all include type: in the initializeDeployment call site, so none will be accidentally blocked.
  • Verified v3.3.7 tarball's initializeDeployment payload has no type field.

Rollout plan after merge:

  1. Deploy with DEPRECATE_V3_CLI_DEPLOYS_ENABLED unset → watch Detected deploy from deprecated v3 CLI log volume.
  2. When comfortable, set DEPRECATE_V3_CLI_DEPLOYS_ENABLED=1 to enforce.

Changelog

Detect v3 CLI deploys on /api/v1/deployments and, when DEPRECATE_V3_CLI_DEPLOYS_ENABLED=1, reject them with an upgrade message pointing at https://trigger.dev/docs/migrating-from-v3. v4 CLI deploys are unaffected.

Link to Devin session: https://app.devin.ai/sessions/b242c11bd86e4099aeec8b59bab62143
Requested by: @ericallam

Detect deploys coming from v3 CLI versions (payloads that omit the
'type' field) and, when DEPRECATE_V3_CLI_DEPLOYS_ENABLED=1, reject them
with a clear error that points to the migration docs. Enforcement is
gated so we can observe v3 deploy traffic via logs before flipping.

v4 CLIs always send 'type' ('MANAGED' or 'V1') on /api/v1/deployments,
so they are unaffected. Verified against the CLI source for 4.0.0,
4.0.1, 4.0.5, 4.1.0, 4.2.0, and 4.4.4.
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 20, 2026

⚠️ No Changeset found

Latest commit: 6edebde

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 20, 2026

Walkthrough

The pull request introduces a server-side deprecation gate for v3 CLI deploys. A new environment variable DEPRECATE_V3_CLI_DEPLOYS_ENABLED is added to the environment schema. The deployment initialization service is modified to detect v3 CLI deploys (identified by absent payload.type), log a warning, and reject them with a validation error when the flag is enabled, directing users to upgrade to CLI v4. V4 CLI deploys remain unaffected. Documentation describes the change.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding server-side deprecation for v3 CLI deploys.
Description check ✅ Passed The description is comprehensive and follows the template with all required sections completed: checklist, summary, testing, and changelog are all present and detailed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch devin/1776690995-deprecate-v3-cli-deploys

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment thread apps/webapp/app/v3/services/initializeDeployment.server.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/webapp/app/v3/services/initializeDeployment.server.ts (1)

28-82: ⚠️ Potential issue | 🟠 Major

Move the v3 gate before the legacy deployment shortcut.

Line 29 can return before Line 67, so a missing-type request with gitMeta.commitSha starting with "deployment_" bypasses both the warning and enforcement. Put the gate first so detection always runs.

🐛 Proposed fix
     return this.traceWithEnv("call", environment, async () => {
+      // v4 CLI versions always send `payload.type` ("MANAGED" or "V1"). v3 CLI
+      // versions never do, so the absence of `type` is a reliable signal that
+      // the request came from a 3.x CLI. Detection always runs (so we can
+      // observe how many deploys are still using v3), enforcement is gated
+      // behind DEPRECATE_V3_CLI_DEPLOYS_ENABLED so it can be rolled out safely.
+      if (!payload.type) {
+        const enforced = env.DEPRECATE_V3_CLI_DEPLOYS_ENABLED === "1";
+
+        logger.warn("Detected deploy from deprecated v3 CLI", {
+          environmentId: environment.id,
+          projectId: environment.projectId,
+          organizationId: environment.project.organizationId,
+          enforced,
+        });
+
+        if (enforced) {
+          throw new ServiceValidationError(
+            "The trigger.dev CLI v3 is no longer supported for deployments. Please upgrade your project to v4: https://trigger.dev/docs/migrating-from-v3"
+          );
+        }
+      }
+
       if (payload.gitMeta?.commitSha?.startsWith("deployment_")) {
         // When we introduced automatic deployments via the build server, we slightly changed the deployment flow
         // mainly in the initialization and starting step: now deployments are first initialized in the `PENDING` status
@@
         };
       }
-
-      // v4 CLI versions always send `payload.type` ("MANAGED" or "V1"). v3 CLI
-      // versions never do, so the absence of `type` is a reliable signal that
-      // the request came from a 3.x CLI. Detection always runs (so we can
-      // observe how many deploys are still using v3), enforcement is gated
-      // behind DEPRECATE_V3_CLI_DEPLOYS_ENABLED so it can be rolled out safely.
-      if (!payload.type) {
-        const enforced = env.DEPRECATE_V3_CLI_DEPLOYS_ENABLED === "1";
-
-        logger.warn("Detected deploy from deprecated v3 CLI", {
-          environmentId: environment.id,
-          projectId: environment.projectId,
-          organizationId: environment.project.organizationId,
-          enforced,
-        });
-
-        if (enforced) {
-          throw new ServiceValidationError(
-            "The trigger.dev CLI v3 is no longer supported for deployments. Please upgrade your project to v4: https://trigger.dev/docs/migrating-from-v3"
-          );
-        }
-      }
 
       if (payload.type === "UNMANAGED") {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/webapp/app/v3/services/initializeDeployment.server.ts` around lines 28 -
82, The v3-deprecation detection (the if (!payload.type) block using
env.DEPRECATE_V3_CLI_DEPLOYS_ENABLED, logger.warn and ServiceValidationError)
must run before the legacy shortcut that returns an existing deployment when
payload.gitMeta.commitSha startsWith("deployment_"); move the entire "if
(!payload.type) { ... }" block to immediately after entering the
traceWithEnv("call", environment, async () => { ... }) callback and before the
"if (payload.gitMeta?.commitSha?.startsWith('deployment_'))" branch so
missing-type requests cannot bypass the warning/enforcement while preserving
existing logging and error behavior.
🤖 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/env.server.ts`:
- Around line 351-355: The env var schema uses z.string().default("0") for
DEPRECATE_V3_CLI_DEPLOYS_ENABLED which accepts typos like "true" or "1 "; change
the validator to z.enum(["0","1"]).default("0") so only the exact values "0" or
"1" are allowed, update any related type usages or destructuring that rely on
the old string type if necessary, and keep the same default semantics to ensure
enforcement toggles behave deterministically (refer to the
DEPRECATE_V3_CLI_DEPLOYS_ENABLED symbol in env.server.ts).

---

Outside diff comments:
In `@apps/webapp/app/v3/services/initializeDeployment.server.ts`:
- Around line 28-82: The v3-deprecation detection (the if (!payload.type) block
using env.DEPRECATE_V3_CLI_DEPLOYS_ENABLED, logger.warn and
ServiceValidationError) must run before the legacy shortcut that returns an
existing deployment when payload.gitMeta.commitSha startsWith("deployment_");
move the entire "if (!payload.type) { ... }" block to immediately after entering
the traceWithEnv("call", environment, async () => { ... }) callback and before
the "if (payload.gitMeta?.commitSha?.startsWith('deployment_'))" branch so
missing-type requests cannot bypass the warning/enforcement while preserving
existing logging and error behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 432279cb-11c5-4d8c-ab69-3067d52d16a4

📥 Commits

Reviewing files that changed from the base of the PR and between 6e6deb4 and 6edebde.

📒 Files selected for processing (3)
  • .server-changes/deprecate-v3-cli-deploys.md
  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: typecheck / typecheck
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{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

Files:

  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.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/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Add crumbs as you write code using // @Crumbs comments or `// `#region` `@crumbs blocks. These are temporary debug instrumentation and must be stripped using agentcrumbs strip before merge.

Files:

  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.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/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.ts
**/*.ts{,x}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from @trigger.dev/sdk when writing Trigger.dev tasks. Never use @trigger.dev/sdk/v3 or deprecated client.defineJob.

Files:

  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.ts
apps/webapp/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/webapp.mdc)

apps/webapp/**/*.{ts,tsx}: Access environment variables through the env export of env.server.ts instead of directly accessing process.env
Use subpath exports from @trigger.dev/core package instead of importing from the root @trigger.dev/core path

Use named constants for sentinel/placeholder values (e.g. const UNSET_VALUE = '__unset__') instead of raw string literals scattered across comparisons

Files:

  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.ts
apps/webapp/**/*.server.ts

📄 CodeRabbit inference engine (apps/webapp/CLAUDE.md)

apps/webapp/**/*.server.ts: Never use request.signal for detecting client disconnects. Use getRequestAbortSignal() from app/services/httpAsyncStorage.server.ts instead, which is wired directly to Express res.on('close') and fires reliably
Access environment variables via env export from app/env.server.ts. Never use process.env directly
Always use findFirst instead of findUnique in Prisma queries. findUnique has an implicit DataLoader that batches concurrent calls and has active bugs even in Prisma 6.x (uppercase UUIDs returning null, composite key SQL correctness issues, 5-10x worse performance). findFirst is never batched and avoids this entire class of issues

Files:

  • apps/webapp/app/env.server.ts
  • apps/webapp/app/v3/services/initializeDeployment.server.ts
🔇 Additional comments (1)
.server-changes/deprecate-v3-cli-deploys.md (1)

1-6: LGTM.

The server-change note clearly documents the breaking deployment behavior and rollout flag.

Comment thread apps/webapp/app/env.server.ts
@ericallam ericallam merged commit 881288c into main Apr 20, 2026
43 checks passed
@ericallam ericallam deleted the devin/1776690995-deprecate-v3-cli-deploys branch April 20, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants