Skip to content

Conversation

@0ski
Copy link
Collaborator

@0ski 0ski commented Feb 3, 2026

Vercel integration

Desc + Vid coming soon

0ski added 27 commits February 3, 2026 13:58
Add a full Remix route and UI for managing Vercel integration
settings for a project environment. Implement client and server pieces
including forms, validation schemas, presenters, and service calls so
users can view and update connection details, toggle sync/build/deploy
options, select staging environment, and complete or skip onboarding.

Key changes:
- Add new route file handling Vercel settings and onboarding flows.
- Introduce form schemas (zod) for update, disconnect, complete and skip
  onboarding actions and parsing with conform/to-zod.
- Implement UI components and layout using existing primitives
  (Dialog, Button, Fieldset, Select, Switch, etc.) and helper functions
  (formatVercelTargets, cn).
- Wire server-side logic to look up projects, environments, and org
  integrations and to call VercelIntegrationService and presenters.
- Add types for ConnectedVercelProject, integration data and mappings.
- Add imports for utilities: path builders, message redirects,
  logging, session requirement, and typed fetcher helpers.

Reasoning:
- Provide a unified interface to configure and manage Vercel
  integrations per environment, improving developer experience when
  syncing envvars, triggering builds/deploys, and completing onboarding.
Add routes, loader/action, and path helpers to support Vercel integration
management under organization settings.

- Add organizationIntegrationsPath and
  organizationVercelIntegrationPath helpers to pathBuilder for consistent
  settings URLs.
- Implement a new route
  _app.orgs.$organizationSlug.settings.integrations.vercel.tsx that:
  - validates org params and optional configurationId query param,
  - enforces organization membership and returns 404 if not found,
  - loads the organization's Vercel integration, its team/installation
    IDs, and connected projects,
  - provides action handling scaffolding for uninstall intent and
    integrates with existing services/repositories.
- Include types, UI primitives, and data formatting via remix-typedjson
  to render integration details and manage deletion.

Motivation: expose a dedicated settings page to view and manage Vercel
integrations per organization, enable deep linking to specific
integration configurations, and centralize route/path construction for
consistency across the app.
…r Vercel integration

Add optional referralSource handling across auth flows and telemetry to record
where new users originate. Extend telemetry.user.identify to accept
referralSource and consolidate identify properties into a single object
before sending to PostHog. Invoke this when users sign in via GitHub or
magic link: read referralSource cookie, attach it for users created within
the last 30 seconds (treating them as new users), and clear the cookie
after use.

This improves attribution for signups by capturing and sending referral
information for new users while ensuring the referral cookie is removed
after consumption.
Preserve full search params when redirecting unauthenticated users to
login so Vercel callback params (code, configurationId, etc.) are not
lost.

Add handling for Vercel callbacks that arrive without a state
parameter (common for Vercel-side installations). When state is
absent but configurationId is present:
- Query the user's organizations and projects.
- If the user has no organizations, redirect to basic details
  onboarding with the Vercel params preserved.
- If the user has organizations but no projects, redirect to the new
  project creation page for the first org with the Vercel params.
- If the user has orgs and projects, exchange the code for a token,
  fetch the Vercel integration configuration, find the default project
  and environment, and continue the installation flow (including
  generating a state JWT).

Add imports for new path helpers and OAuth state generator,
and wire in logic to build redirect URLs with next param when present.

Improve error handling and user-facing error redirects for token and
configuration fetch failures.
- replace vercelResourcePath with v3ProjectSettingsPath for linking to
  project settings pages so routing uses the v3 path helper.
- move DialogDescription out of DialogHeader and into DialogContent to
  correct markup/structure and restore the form buttons layout inside the
  dialog content.
- remove unused import (confirmBasicDetailsPath) and unused OAuth state
  generator import to keep callback route imports minimal.
- change secret type from "sensitive"/"plain" to "encrypted" when syncing
  Vercel trigger keys to standardize storage semantics.
- fix a stray/partial line in the Vercel callback file (cleanup after
  parameter parsing).

These changes improve routing consistency, correct dialog markup for
accessibility and layout, standardize secret typing, and clean up unused
imports and stray code.
Introduce utilities to robustly handle Vercel API response shapes:
- add normalizeTarget to coerce target values into string arrays
- add extractEnvs to safely pull envs arrays from union responses

Replace ad-hoc existence checks and inline normalization with these helpers
in getVercelEnvironmentVariables and getVercelEnvironmentVariableValues to
avoid runtime errors when API returns varying shapes (string vs array vs
missing). This simplifies mapping logic and centralizes normalization.

Remove several outdated JSDoc comments and compress inline documentation to
keep the implementation focused and more readable.
- Add getVercelInstallParams(request) to extract Vercel installation
  parameters (code, configurationId, next) from a request URL and only
  return them when the integration param is "vercel" or absent. This
  centralizes install URL parsing for Vercel flows.

- Add requireOrganization(request, organizationSlug) helper that
  ensures the caller is an authenticated member of the organization,
  returning the organization and userId or throwing a 404 response.
  This enforces access control for org-scoped routes.

- Refactor EnvironmentVariablesPresenter to use a Vercel integration
  service for fetching project integration and remove in-presenter
  legacy migration/parsing logic. The presenter now relies on
  VercelIntegrationService.getVercelProjectIntegration(projectId, true)
  to obtain parsed integration data, simplifying responsibilities and
  consolidating Vercel-related parsing/migration in the service layer.

These changes improve separation of concerns, centralize Vercel
integration parsing, and add a reusable org auth helper for request
handlers.
Update Vercel org integration handling to update existing integration
tokens instead of always creating a new record. When an existing org
integration is detected, log the update, call the repository update
method with the new token and metadata, and re-fetch the integration to
use the up-to-date record. This avoids duplicate integrations and keeps
stored tokens current.

Refactor Vercel repo imports and environment lookup to improve error
handling and typing. Return early when getVercelCustomEnvironments fails,
use the typed result (.data) and VercelCustomEnvironment for safer
lookup, and remove several commented section headers and unused helpers
to simplify the module. Also remove an unused presenter import and tidy
up minor formatting.

Why: prevent duplicate org integrations, ensure tokens are refreshed
correctly, and make environment name resolution more robust and typed.
- return uninstall result from uninstallVercelIntegration so caller can
  react when Vercel rejects the request due to invalid credentials.
- detect auth errors more robustly in isVercelAuthError by checking
  statusCode fields on non-Axios error shapes.
- treat 401/403 from Vercel as soft-fail: log a warning and continue to
  clean up local DB records instead of aborting, and surface authInvalid
  flag to the route handler.
- log a warning when uninstall succeeds but the token is invalid; log
  normal info for successful uninstalls.
Add typed structures, schema validation, and token exchange helper
for the Vercel OAuth callback flow. Introduce TokenResponse,
ProjectWithOrganization, StateData, and CallbackParams types to make
intent explicit and reduce implicit any usage.

Add VercelCallbackSchema (zod) to validate incoming callback query
parameters and ensure safer parsing of code/state/error payloads.

Implement exchangeCodeForToken helper that posts to Vercel's token
endpoint, handles errors, logs failures, and returns a normalized token
shape (including raw response). This centralizes HTTP logic and
improves error handling.

Refactor createOrFindVercelIntegration to use the new TokenResponse and
ProjectWithOrganization types, simplify flow comments, and avoid
repetitive code by updating/creating integrations with consistent data.

Overall this change improves type safety, error reporting, and
readability of the Vercel OAuth callback route.
Add a new Remix route apps/webapp/app/routes/onboarding.vercel.tsx
that implements server loader and token exchange to support Vercel
integration onboarding. Introduce schemas (zod) for loader query
parameters and form actions, and add helper exchangeCodeForToken to
call Vercel's OAuth token endpoint with robust error logging.

Fetch the current user's organizations and projects from Prisma in the
loader, validate incoming query params, and handle missing/misconfigured
Vercel client credentials with clear log messages. Add typed TokenResponse
shape, use env variables for client credentials and redirect URI, and
use redirectWithErrorMessage for user-facing failures.

Motivation: enable users to connect a Vercel integration during
onboarding by validating parameters, exchanging OAuth codes, and
presenting organization/project selection backed by the app database.
Add support for VERCEL_INTEGRATION_APP_SLUG and require it for Vercel
integration availability checks. Update the Vercel install URL and redirect
callback to use the configured app slug and the /vercel/callback path so
install flows from Vercel Marketplace route correctly back to the app.

Improve route and component code:
- Add loader comment to vercel.configure route.
- Remove unused conform import and unused OrgIntegrationRepository.
- Add Tooltip primitives import and utility shortEnvironmentLabel for
  environment display.
- Consolidate and reuse v3ProjectSettingsPath earlier in the handler to
  avoid duplication.
- Remove unused cn and message redirect import cleanup.

These changes fix Marketplace installation URL construction, ensure the
integration is only advertised when fully configured, and tidy up
imports and handler flow for clearer UI logic.

Move code around, so it’s easier to understand. Make the data flow more
transparent. 
For dashboard initialized flow:
vercel/install -> vercel -> vercel/callback -> vercel/connect
For marketplace initialized flow:
Vercel -> vercel/callback -> vercel/onboarding -> vercel/connect
Update onboarding flow to propagate the selected Vercel environment
when reloading data and move the OAuth token exchange into the
VercelIntegration repository.

- Add vercelEnvironmentId parameter to onDataReload and include it in
  the vercelFetcher load URL so the UI can reload with the specific
  environment context.
- Remove local exchangeCodeForToken implementation from the route and
  call VercelIntegrationRepository.exchangeCodeForToken instead to
  centralize OAuth token exchange logic.
- Rework redirects and settings path handling to compute settingsPath
  earlier and reuse it consistently on success and error paths.
- Import env into the vercel integration model file (prepares use of
  configuration from env.server).

These changes improve consistency, reduce duplicated OAuth code, and
ensure environment-specific reloads work correctly.
…tting names

- Query existing environment variable values to detect which keys
  are currently marked as secrets and build a set of secret keys.
- Split variables into secret and non-secret groups so non-secrets
  can be created without unintentionally overriding secret flags.
- Create non-secret variables via envVarRepository.create and update
  synced counters and error handling accordingly.
- Rename the Vercel integration settings to be more meaningful
- Iterate on Vercel onboarding flow, add GitHub step
Query and read existing environment variable secret values from the
secret store and track which vars are marked secret. Compare current
values to the incoming Vercel values and skip any variables whose
stored secret value matches the incoming value. Log when no changes
are detected and when only changed vars are updated. Also adjust the
split between secret and non-secret groups to operate on the filtered
changed set.

Additionally, avoid redundant secret writes when creating/updating
secret references in the environment variables repository by checking
the secret store first and skipping unchanged values.

These changes reduce unnecessary secret writes and API calls, preserve
secret status when overriding, and add diagnostic logging for skipped
and changed variables.
Add a version integer (default 1) and a lastUpdatedBy JSON field to the
EnvironmentVariableValue model. Include a migration that alters the
EnvironmentVariableValue table to add the version column (NOT NULL,
default 1) and the lastUpdatedBy JSONB column.

This enables simple optimistic versioning and stores metadata about the
actor that last updated an environment variable, improving concurrency
control and auditability.
Add a discriminated EnvironmentVariableUpdater schema to represent who
last updated a variable (user or integration) and thread it through
create/edit/value types. Persist lastUpdatedBy and version fields in
presenter queries, collect referenced user records, and resolve a
display-friendly updatedBy user object (id, name, avatar) for each
environment variable value. Also ensure isSecret is read from the value
record and avoid returning secrets' values.

These changes allow auditing of who modified env vars (user vs
integration), enable showing updater details in the UI, and make the
presenter robust against missing value records.
Add pullNewEnvVars to Vercel integration types, schema, defaults,
and parsing so integrations can opt into discovering new environment
variables from Vercel during builds.

- Add pullNewEnv boolean to VercelProjectIntegrationData and to the
  Vercel integration Zod schema (nullable/optional).
- Default pullNewEnvVars to true in createDefaultVercelIntegrationData
  and update defaults for atomicBuilds and pullEnvVarsBeforeBuild to
  include non-dev environments.
- Add helper isPullNewEnvVarsEnabled to coerce undefined/null to true.
- Parse pullNewEnvVars from route form input (string -> boolean|null).

Also improve GitHub connect modal handling:

- Add preventDismiss prop to the connect GitHub modal component.
- Prevent closing the modal via Escape key or clicking outside when
  preventDismiss is true by blocking onInteractOutside/onEscapeKeyDown
  and refusing to change open state when attempting to close.

These changes enable automatic discovery of new Vercel env vars by
default and provide a way to force modal persistence during critical
flows.
…pport

Refactor the Vercel integration to change the structure of the
`vercelStagingEnvironment` to an object containing `environmentId` and
`displayName`. Update related logic to utilize the new structure,
ensuring that the environment ID is correctly referenced in various
parts of the application.Additionally, introduce support for a preview
environment by adding `hasPreviewEnvironment` and `customEnvironments`
to the Vercel settings presenter. Update the UI components and routes to
accommodate these changes, allowing for better management of Vercel
environments in the application.
Change default Vercel integration data to enable atomic builds only
for production and keep pull env vars enabled for all non-dev
environments. This tightens build behavior to match expected defaults
and avoids enabling atomic builds for staging/preview by default.

Add optional integrationDeployments field to deployment schema and
include it in the deployment API response. This introduces a typed
array of integration deployment records (id, integrationName,
integrationDeploymentId, commitSHA, createdAt) so downstream clients
can surface related integration deployment metadata.

Expose Vercel project setting autoAssignCustomDomains in presenter by
fetching it alongside custom environments. Combine fetching of custom
environments and the auto-assign setting into a single helper that
returns both values (or null when unknown), and thread the new
autoAssignCustomDomains flag through the presenter types.

Also rename a UI label from "Pull from Vercel" to "Sync" for clarity.
Adjust column widths and consolidate the "Updated" columns into a
single cell to simplify the table header and row layout. Increase
Key/Value/Environment header widths slightly to better match content
when Vercel integration is enabled. Replace separate columns for
version, updated-by, and updated-at with one "Updated" column that
renders the actor (user avatar/name or Vercel integration) and the
timestamp together.

Also simplify the Actions header sizing to remove a tiny fixed width
and use hiddenLabel with zero width, improving responsiveness.

These changes clean up the table markup, reduce cluttered columns,
and improve alignment and responsiveness of the environment variables
list.
Add an optional discriminated `source` field to the import request schema
to capture whether variables are imported by a user or an integration and to
include the corresponding identifier (userId or integration). Propagate this
source into the database payloads (`lastUpdatedBy`) when creating or
syncing environment variables so the system records who/what initiated the
import.
Add a new component, BuildSettingsFields, to manage Vercel build settings, including options for pulling environment variables before builds, discovering new environment variables, and enabling atomic deployments. Update related routes and schemas to accommodate the new discoverEnvVars functionality, replacing the previous pullNewEnvVars option. This enhances the Vercel integration by providing a more granular control over environment variable management during builds.
Introduce a new VercelOnboardingModal component to streamline the onboarding process for Vercel integration. This modal manages project selection, environment variable synchronization, and build settings configuration. It enhances user experience by providing a structured flow for onboarding, including handling custom environments and GitHub integration. Additionally, refactor related logic in the Vercel integration service and update schemas to support the new onboarding features.
@changeset-bot
Copy link

changeset-bot bot commented Feb 3, 2026

🦋 Changeset detected

Latest commit: 29a3434

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 28 packages
Name Type
@trigger.dev/core Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@trigger.dev/sdk Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
references-telemetry Patch
@internal/sdk-compat-tests Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch

Not sure what this means? Click here to learn what changesets are.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

Walkthrough

Adds a full Vercel integration: OAuth connect and install flows, onboarding and settings UI, server-side repositories/services for token exchange, API calls, env var sync and project selection, and database migrations for integration records, integration deployments, and environment-variable versioning. Exposes new routes (connect, callback, install, onboarding, configure, resource APIs), path helpers, presenters, and presenters' payloads. Also adds referral-source tracking, telemetry updates, and multiple UI components and schema/type changes across the webapp.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete. It lacks required sections: testing steps, detailed changelog, and screenshots. Only a placeholder 'Desc + Vid coming soon' is provided. Complete the PR description by filling out all template sections: provide testing steps, a detailed changelog describing changes, and any relevant screenshots.
Docstring Coverage ⚠️ Warning Docstring coverage is 7.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(webapp): Vercel integration' clearly and concisely summarizes the main change—adding Vercel integration to the webapp.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch oskar/feat-vercel-integration

Important

Action Needed: IP Allowlist Update

If your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:

  • 136.113.208.247/32 (new)
  • 34.170.211.100/32
  • 35.222.179.152/32

Reviews will stop working after February 8, 2026 if the new IP is not added to your allowlist.


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
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: 19

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/environmentVariables/environmentVariablesRepository.server.ts (1)

183-210: ⚠️ Potential issue | 🟠 Major

Don’t skip isSecret changes when the value is unchanged.

If options.isSecret is provided (e.g., overriding to secret) and the secret value matches, the early continue prevents updating isSecret (and skips creation when a value record is missing). This breaks “mark as secret” with unchanged values.

🔧 Proposed fix
-            // Check if value already exists and is the same - skip update if unchanged
-            const existingSecret = await secretStore.getSecret(SecretValue, key);
-            if (existingSecret && existingSecret.secret === variable.value) {
-              // Value is unchanged, skip this variable for this environment
-              continue;
-            }
-
-            const existingValueRecord = await tx.environmentVariableValue.findFirst({
+            const existingValueRecord = await tx.environmentVariableValue.findFirst({
               where: {
                 variableId: environmentVariable.id,
                 environmentId,
               },
             });
+
+            // Check if value already exists and is the same - skip update if unchanged *and*
+            // there is no metadata change (e.g. isSecret toggle)
+            const existingSecret = await secretStore.getSecret(SecretValue, key);
+            const canSkip =
+              existingSecret &&
+              existingSecret.secret === variable.value &&
+              existingValueRecord &&
+              (options.isSecret === undefined ||
+                existingValueRecord.isSecret === options.isSecret);
+            if (canSkip) {
+              continue;
+            }
🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 323-324: Remove the debug console.log call in
VercelOnboardingModal and either delete the line or replace it with your app's
proper logging utility (e.g., processLogger.info or useLogger().info) before
calling onDataReload(); specifically remove the line containing
console.log("Vercel onboarding: Reloading data...") in the block that calls
onDataReload() so production code has no console.log usage and any message uses
the established logger API.

In `@apps/webapp/app/models/vercelIntegration.server.ts`:
- Around line 22-48: The isVercelSecretType function currently returns true only
for "secret" and "sensitive", so Vercel env vars with type "encrypted" are
treated as non-secret; update isVercelSecretType to also return true for
"encrypted" (i.e., treat "encrypted" as a secret-intended type) so secret
detection in sync logic (isVercelSecretType) correctly classifies encrypted
variables as secrets.

In `@apps/webapp/app/presenters/v3/BranchesPresenter.server.ts`:
- Line 45: The GitMeta schema's source enum is missing "trigger_vercel_app",
causing GitMeta.safeParse in processGitMetadata to reject Vercel-origin
metadata; update the GitMeta z.enum definition in the common schema (the one
referenced by GitMeta.safeParse and used alongside GitMetaLinks) to include
"trigger_vercel_app" alongside "trigger_github_app", "github_actions", and
"local" so Vercel-sourced git metadata is validated and preserved.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:
- Around line 650-651: The delete Button currently renders an empty label when
idle ({isLoading ? "Deleting" : ""}), making the action undiscoverable; update
the Button's children to show a default label (e.g., {isLoading ? "Deleting" :
"Delete"}) and add an explicit accessible name if missing (aria-label="Delete
variable" or similar) on the same Button instance to preserve clarity while
loading and improve accessibility.
- Around line 553-555: The menu Button (component named Button with props
LeadingIcon={PencilSquareIcon}) is rendering empty because it has no child/text;
restore the visible label by adding the string "Edit" as the Button's child (or
use the Button's text prop if that pattern is used elsewhere) so the menu item
shows "Edit" alongside the PencilSquareIcon in route.tsx where the Button is
declared.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx:
- Line 315: The fetcher is typed as any—replace useTypedFetcher<any>() with a
correctly typed fetcher by importing the loader (response) type from the Vercel
resource route and using it as the generic parameter; e.g., import the loader's
Response/LoaderData type used by that route and change the declaration of
vercelFetcher (the call to useTypedFetcher) to use that specific type instead of
any so TypeScript enforces the actual data shape.

In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.integrations.vercel.tsx:
- Around line 118-120: ActionSchema is defined but never used; update the
route's action function to parse and validate the incoming form data with
ActionSchema (e.g. call await request.formData(), extract the "intent" value,
and use ActionSchema.parse or safeParse). If validation fails return a 400
response or handle the error path; on success proceed with the existing
uninstall logic. Reference ActionSchema and the action function exported from
this route when making the change.
- Around line 350-354: There is a duplicate empty-state branch: the conditional
rendering checking connectedProjects.length === 0 that returns a <TableBlankRow>
is unreachable because an earlier check already renders a different empty state;
remove the redundant block (the conditional containing TableBlankRow) so only
the original empty-state UI remains and ensure no other JSX references the
removed fragment (look for connectedProjects and TableBlankRow usages to locate
the block).

In
`@apps/webapp/app/routes/api.v1.orgs`.$organizationSlug.projects.$projectParam.vercel.projects.ts:
- Around line 27-71: The loader currently returns Vercel integration metadata
without authenticating or checking org membership; call
authenticateApiRequestWithPersonalAccessToken at the start of loader (after the
OPTIONS/CORS handling) to authenticate the requester, then verify the
authenticated user's membership in the organization identified by
organizationSlug (use the same membership-check logic/pattern used in
api.v1.orgs.$orgParam.projects.ts — e.g., lookup organization membership via
prisma using the authenticated user's id and organizationSlug) and return a
403/json error via apiCors if not a member; only after authentication and
membership check proceed to call
VercelIntegrationService.getVercelProjectIntegration and return the integration
details.

In `@apps/webapp/app/routes/login.mfa/route.tsx`:
- Around line 166-190: The session.unset(...) changes to the user session are
not being persisted because only authSession is committed; after you call
session.unset for the pending-mfa-* fields (the session object used for the
logged-in user), call await sessionStorage.commitSession(session) and append its
Set-Cookie header (e.g. headers.append("Set-Cookie", await
sessionStorage.commitSession(session))) so the cleared pending-mfa-* values are
written to the cookie; ensure you commit this user session before
returning/setting other cookies (keep existing authSession commit intact).

In
`@apps/webapp/app/routes/resources.environments`.$environmentId.regenerate-api-key.tsx:
- Around line 27-33: The call to syncApiKeyToVercelInBackground is
fire-and-forget and may be terminated in serverless runtimes; change the code to
await syncApiKeyToVercelInBackground(...) inside a try/catch so the request
waits for completion and logs any errors (use updatedEnvironment.projectId,
updatedEnvironment.type, updatedEnvironment.apiKey), or alternatively dispatch
the same payload to a durable background job/queue and await enqueue
confirmation; ensure you do not leave an unhandled promise and add error
handling/logging around the call.

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 772-773: The envVarsConfigLink prop is missing the environment
scope; update the usage of envVarsConfigLink (where it's currently built from
organizationSlug and projectSlug) to include the environmentSlug from the loader
so the path becomes the canonical
/orgs/{organizationSlug}/projects/{projectSlug}/env/{environmentSlug}/environment-variables;
locate the JSX/prop that sets envVarsConfigLink and insert environmentSlug
between projectSlug and environment-variables (using the existing
environmentSlug variable).

In `@apps/webapp/app/routes/vercel.callback.ts`:
- Around line 9-18: The VercelCallbackSchema currently declares configurationId
as required (VercelCallbackSchema -> configurationId: z.string()) but the
handler logic later treats configurationId as optional (checks if
(configurationId)); make these consistent by updating the schema to
configurationId: z.string().optional() so the parsed payload matches the
conditional, and keep the existing runtime check, or alternatively remove the
runtime if (configurationId) guard and leave the schema required—choose the
former if the code must accept callbacks without configurationId.

In `@apps/webapp/app/routes/vercel.onboarding.tsx`:
- Line 268: The isInstalling loading flag (useState in this file) is set true on
form submit but never reset on error; update the submit flow that calls
setIsInstalling(true) so it also sets setIsInstalling(false) when the action
returns an error or throws—e.g., after awaiting the action response (or in the
catch/finally) inspect actionData or the thrown error and call
setIsInstalling(false) to unblock the UI; reference the isInstalling state and
setIsInstalling function and the form submit/submit handler where you currently
set it true to add this reset.

In `@apps/webapp/app/services/vercelIntegration.server.ts`:
- Around line 398-408: The code building updatedData ignores
params.syncEnvVarsMapping so onboarding selections aren't persisted; update the
construction of updatedData (VercelProjectIntegrationData) to set
syncEnvVarsMapping using params.syncEnvVarsMapping when provided (e.g.,
syncEnvVarsMapping: params.syncEnvVarsMapping ??
existing.parsedIntegrationData.syncEnvVarsMapping) instead of always copying
existing.parsedIntegrationData.syncEnvVarsMapping so new mappings from params
are saved.

In `@apps/webapp/app/v3/vercel/index.ts`:
- Around line 3-14: getVercelInstallParams currently returns the raw next query
param; update it to sanitize the next value at the source by importing and using
sanitizeRedirectPath (same helper used by other routes) and only include next in
the returned object if sanitizeRedirectPath(next) yields a truthy/safe value;
keep existing checks on code/configurationId/integration and return null
otherwise.

In
`@internal-packages/database/prisma/migrations/20260129162810_add_integration_deployment/migration.sql`:
- Around line 14-18: Move the two index creations out of the current migration
and into a new, separate migration file and create them with CONCURRENTLY to
avoid table locks: remove the CREATE INDEX
"IntegrationDeployment_deploymentId_idx" and CREATE INDEX
"IntegrationDeployment_commitSHA_idx" statements from the current migration and
add a new migration containing CREATE INDEX CONCURRENTLY
"IntegrationDeployment_deploymentId_idx" ON
"public"."IntegrationDeployment"("deploymentId"); and CREATE INDEX CONCURRENTLY
"IntegrationDeployment_commitSHA_idx" ON
"public"."IntegrationDeployment"("commitSHA");; ensure the new migration is
executed non-transactionally (since CONCURRENTLY cannot run inside a
transaction) and update any migration metadata accordingly.

In `@internal-packages/database/prisma/schema.prisma`:
- Around line 1861-1862: The inline indexes for IntegrationDeployment
(IntegrationDeployment_commitSHA_idx and IntegrationDeployment_deploymentId_idx)
must be removed from the model (remove the @@index([...]) entries in the
IntegrationDeployment model in schema.prisma) and instead added in a separate
SQL migration file that runs outside a transaction and uses CREATE INDEX
CONCURRENTLY for both indexes (mirror how WorkerDeployment_commitSHA_idx and
OrganizationIntegration_externalOrganizationId_idx were separated). Update the
migration named 20260129162810_add_integration_deployment by removing any
bundled index creation and create a new migration that executes "CREATE INDEX
CONCURRENTLY ..." for IntegrationDeployment_commitSHA_idx and
IntegrationDeployment_deploymentId_idx so the table is not locked during
deployment.

In `@packages/core/src/v3/schemas/api.ts`:
- Around line 1155-1160: Add a changeset documenting the schema modification to
the discriminated union in packages/core (the source schema defined with
z.discriminatedUnion("type", ...) in packages/core/src/v3/schemas/api.ts); run
`pnpm run changeset:add`, select the packages/core package, write a brief
description that this change adds/modifies the optional "source" discriminated
union (user vs integration) and mark it as a patch release, then commit the
generated changeset file with the PR.
🧹 Nitpick comments (23)
.vscode/settings.json (1)

10-11: Consider team impact of experimental settings.

Enabling typescript.experimental.useTsgo may cause inconsistent IDE behavior across team members with different VS Code versions. Consider moving experimental/personal preferences to user settings rather than workspace settings, or document the requirement in the README.

apps/webapp/app/components/integrations/VercelLogo.tsx (1)

1-12: Consider adding accessibility attributes.

The SVG lacks accessibility attributes. For decorative icons, add aria-hidden="true" to hide from screen readers. If the icon conveys meaning, add role="img" and an aria-label.

♿ Proposed accessibility improvement
 export function VercelLogo({ className }: { className?: string }) {
   return (
     <svg
       viewBox="0 0 76 65"
       fill="none"
       xmlns="http://www.w3.org/2000/svg"
       className={className}
+      aria-hidden="true"
     >
       <path d="M37.5274 0L75.0548 65H0L37.5274 0Z" fill="currentColor" />
     </svg>
   );
 }
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx (1)

540-544: Consider hiding the Cancel button when preventDismiss is true.

The DialogClose Cancel button can still close the modal even when preventDismiss is enabled. If the intent is to force users to complete the action, consider conditionally hiding or disabling this button:

💡 Conditional Cancel button
               cancelButton={
-                  <DialogClose asChild>
-                    <Button variant="tertiary/medium">Cancel</Button>
-                  </DialogClose>
+                  !preventDismiss ? (
+                    <DialogClose asChild>
+                      <Button variant="tertiary/medium">Cancel</Button>
+                    </DialogClose>
+                  ) : undefined
               }

Note: If explicit cancellation is intentionally allowed while blocking accidental dismissal, this change is not needed.

apps/webapp/app/services/org.server.ts (1)

15-17: Consider using JSON error response for consistency.

Other API routes in the codebase return JSON error responses (e.g., json({ error: "..." }, { status: 404 })). Using a plain text Response here may be inconsistent with the rest of the application.

Suggested change
+import { json } from "@remix-run/node";
+
 export async function requireOrganization(request: Request, organizationSlug: string) {
   // ...
   if (!organization) {
-    throw new Response("Organization not found", { status: 404 });
+    throw json({ error: "Organization not found" }, { status: 404 });
   }
apps/webapp/app/routes/vercel.configure.tsx (1)

16-18: Use safeParse for better error handling.

Using parse directly will throw an unhandled error, resulting in a 500 response. Consider using safeParse to return a proper 400 response for invalid/missing parameters.

Suggested improvement
 export const loader = async ({ request }: LoaderFunctionArgs) => {
   const url = new URL(request.url);
   const searchParams = Object.fromEntries(url.searchParams);
   
-  const { configurationId } = SearchParamsSchema.parse(searchParams);
+  const parsed = SearchParamsSchema.safeParse(searchParams);
+  
+  if (!parsed.success) {
+    throw new Response("Missing or invalid configurationId", { status: 400 });
+  }
+  
+  const { configurationId } = parsed.data;
apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts (1)

73-82: Rename iterator variable for clarity.

The variable id is misleading since it represents an IntegrationDeployment record, not an ID. This makes id.id on line 76 particularly confusing.

Suggested improvement
     integrationDeployments:
       deployment.integrationDeployments.length > 0
-        ? deployment.integrationDeployments.map((id) => ({
-            id: id.id,
-            integrationName: id.integrationName,
-            integrationDeploymentId: id.integrationDeploymentId,
-            commitSHA: id.commitSHA,
-            createdAt: id.createdAt,
+        ? deployment.integrationDeployments.map((integrationDeployment) => ({
+            id: integrationDeployment.id,
+            integrationName: integrationDeployment.integrationName,
+            integrationDeploymentId: integrationDeployment.integrationDeploymentId,
+            commitSHA: integrationDeployment.commitSHA,
+            createdAt: integrationDeployment.createdAt,
           }))
         : undefined,
apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx (1)

118-124: Consider linking to a generic integrations page.

The menu item is labeled "Integrations" (plural) but links directly to the Vercel-specific page (organizationVercelIntegrationPath). If additional integrations are planned, consider linking to organizationIntegrationsPath(organization) instead, which would be the parent /settings/integrations route. This would allow for a landing page listing all available integrations.

If Vercel is intended to be the only integration for now, this is acceptable, but the naming could be misleading to users.

apps/webapp/app/utils/pathBuilder.ts (1)

170-178: Duplication of vercelResourcePath exists but may be contextually justified.

Two versions exist: pathBuilder.ts expects typed objects (OrgForPath, ProjectForPath, EnvironmentForPath with slug properties), while the vercel.tsx version accepts raw strings (organizationSlug, projectSlug, environmentSlug). The vercel.tsx route receives route params as strings and has no need to construct full typed objects just to extract their slug property. While the duplication is real, consolidating toward the pathBuilder version would require unnecessary object construction in the route handler. Consider this acceptable duplication given the different input scenarios, or add a comment explaining why the local version exists.

apps/webapp/app/models/orgIntegration.server.ts (1)

101-106: Consider URL-encoding the state parameter.

The state parameter is passed directly into the URL without encoding. If the base64-encoded state contains URL-unsafe characters like +, /, or =, this could cause issues with the OAuth callback.

♻️ Suggested fix
   static vercelInstallUrl(state: string): string {
     // The user goes to Vercel's marketplace to install the integration
     // After installation, Vercel redirects to our callback with the authorization code
     const redirectUri = encodeURIComponent(`${env.APP_ORIGIN}/vercel/callback`);
-    return `https://vercel.com/integrations/${env.VERCEL_INTEGRATION_APP_SLUG}/new?state=${state}&redirect_uri=${redirectUri}`;
+    return `https://vercel.com/integrations/${env.VERCEL_INTEGRATION_APP_SLUG}/new?state=${encodeURIComponent(state)}&redirect_uri=${redirectUri}`;
   }
apps/webapp/app/routes/auth.github.callback.tsx (1)

59-77: Duplicate user query could be consolidated.

The user is queried twice: once at lines 24-32 (selecting only id and mfaEnabledAt) and again at lines 61-63 (full user). Consider fetching createdAt in the first query to avoid the redundant database call.

♻️ Suggested optimization
   const userRecord = await prisma.user.findFirst({
     where: {
       id: auth.userId,
     },
     select: {
       id: true,
       mfaEnabledAt: true,
+      createdAt: true,
+      email: true,
+      name: true,
+      admin: true,
     },
   });

Then reuse userRecord instead of querying again:

   const referralSource = await getReferralSource(request);
   if (referralSource) {
-    const user = await prisma.user.findUnique({
-      where: { id: auth.userId },
-    });
-    if (user) {
-      const userAge = Date.now() - user.createdAt.getTime();
+    if (userRecord) {
+      const userAge = Date.now() - userRecord.createdAt.getTime();
       const isNewUser = userAge < 30 * 1000;
       
       if (isNewUser) {
         telemetry.user.identify({
-          user,
+          user: userRecord,
           isNewUser: true,
           referralSource,
         });
       }
     }
     headers.append("Set-Cookie", await clearReferralSourceCookie());
   }
apps/webapp/app/routes/magic.tsx (1)

58-79: Consider extracting referral/telemetry handling into a shared helper.

The same block now appears in magic login, MFA completion, and Google callback. A shared helper would reduce duplication and keep behavior consistent.

apps/webapp/app/routes/vercel.connect.tsx (1)

20-63: Avoid the redundant project lookup.

project (with organization) is already fetched in the loader; consider passing it into createOrFindVercelIntegration to save a DB round‑trip.

apps/webapp/app/routes/vercel.callback.ts (1)

5-5: Unused import: requireUserId.

The requireUserId function is imported but never used in this file. Only getUserId is called.

🧹 Remove unused import
-import { getUserId, requireUserId } from "~/services/session.server";
+import { getUserId } from "~/services/session.server";
apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts (2)

78-92: Consider extracting the type guard for cleaner code.

The inline type guard for lastUpdatedBy is verbose. A named type guard function would improve readability and reusability.

♻️ Extract type guard
function isUserUpdater(
  value: unknown
): value is { type: "user"; userId: string } {
  return (
    value !== null &&
    typeof value === "object" &&
    "type" in value &&
    value.type === "user" &&
    "userId" in value &&
    typeof value.userId === "string"
  );
}

// Then use:
const userIds = new Set(
  environmentVariables
    .flatMap((envVar) => envVar.values)
    .map((value) => value.lastUpdatedBy)
    .filter(isUserUpdater)
    .map((lastUpdatedBy) => lastUpdatedBy.userId)
);

141-151: Service instantiation reduces testability.

Creating VercelIntegrationService inline makes the presenter harder to test in isolation. Consider injecting it through the constructor or as a parameter.

♻️ Inject service for testability
 export class EnvironmentVariablesPresenter {
   `#prismaClient`: PrismaClient;
+  `#vercelService`: VercelIntegrationService;

-  constructor(prismaClient: PrismaClient = prisma) {
+  constructor(
+    prismaClient: PrismaClient = prisma,
+    vercelService?: VercelIntegrationService
+  ) {
     this.#prismaClient = prismaClient;
+    this.#vercelService = vercelService ?? new VercelIntegrationService(prismaClient);
   }

   // Then use this.#vercelService instead of creating new instance

As per coding guidelines: "Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts"

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (1)

343-381: Multiple overlapping useEffects for modal state create complexity.

Three separate useEffects (lines 343-361, 365-371, 374-381) all manage modal opening based on query params. This creates hard-to-follow control flow and potential for race conditions.

Consider consolidating into a single effect or using a state machine pattern.

♻️ Consolidate modal state management
// Single effect to manage modal state based on query param and data
useEffect(() => {
  if (!vercelIntegrationEnabled) return;
  
  if (hasQueryParam) {
    // Query param present - ensure modal is open and data is loaded
    if (!isModalOpen) {
      setIsModalOpen(true);
    }
    if (vercelFetcher.state === "idle" && !vercelFetcher.data?.onboardingData) {
      vercelFetcher.load(
        `${vercelResourcePath(organization.slug, project.slug, environment.slug)}?vercelOnboarding=true`
      );
    }
  } else if (isModalOpen) {
    // Query param removed - close modal
    setIsModalOpen(false);
  }
}, [hasQueryParam, vercelIntegrationEnabled, isModalOpen, vercelFetcher.state, vercelFetcher.data?.onboardingData]);
apps/webapp/app/routes/vercel.onboarding.tsx (1)

290-301: Duplicated URL construction logic.

The newOrgUrl and newProjectUrl construction share similar logic for building params with code, configurationId, integration, and next. Consider extracting a helper.

♻️ Extract URL builder helper
function buildIntegrationParams(data: { 
  code: string; 
  configurationId: string | null; 
  next: string | null;
  organizationId?: string;
}): URLSearchParams {
  const params = new URLSearchParams();
  params.set("code", data.code);
  if (data.configurationId) params.set("configurationId", data.configurationId);
  params.set("integration", "vercel");
  if (data.organizationId) params.set("organizationId", data.organizationId);
  if (data.next) params.set("next", data.next);
  return params;
}

Also applies to: 363-375

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (2)

434-434: Unused nextUrl in dependency array.

The handleSkipOnboarding callback includes nextUrl in its dependency array but doesn't use it in the function body.

🧹 Remove unused dependency
-  }, [actionUrl, fetcher, onClose, nextUrl, fromMarketplaceContext]);
+  }, [actionUrl, fetcher, onClose, fromMarketplaceContext]);

66-77: Complex state machine implemented with multiple useEffects.

The onboarding flow uses ~10 useEffects to manage state transitions. This creates implicit control flow that's difficult to trace and test. Consider using a dedicated state machine library (like XState) or consolidating into a reducer pattern.

This is a significant refactor but would greatly improve maintainability. The current implementation works but the implicit dependencies between effects make it fragile to future changes.

Also applies to: 119-329

apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (2)

7-7: Unused import: useLoaderData.

useLoaderData is imported but useTypedLoaderData is used instead.

🧹 Remove unused import
-import { Form, useActionData, useLoaderData, useNavigation } from "@remix-run/react";
+import { Form, useActionData, useNavigation } from "@remix-run/react";

85-87: Avoid any cast for integration data.

Casting integrationData to any loses type safety. Define a proper type for the Vercel integration data structure.

🔧 Add proper typing
+type VercelIntegrationData = {
+  teamId?: string;
+  installationId?: string;
+};

 // Get team ID from integrationData
-const integrationData = vercelIntegration.integrationData as any;
+const integrationData = vercelIntegration.integrationData as VercelIntegrationData | null;
 const teamId = integrationData?.teamId ?? null;
 const installationId = integrationData?.installationId ?? null;
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (2)

3-6: Derive ALL_ENV_SLUGS from EnvSlugSchema to prevent drift.

This keeps the constant and schema aligned if new slugs are added.

♻️ Proposed change
-export const ALL_ENV_SLUGS: EnvSlug[] = ["dev", "stg", "prod", "preview"];
+export const ALL_ENV_SLUGS: EnvSlug[] = EnvSlugSchema.options;

125-149: Consider centralizing env type/slug mapping.

envTypeToSlug overlaps with envSlug in apps/webapp/app/models/api-key.server.ts. Consolidating into a shared helper reduces drift risk.

Comment on lines +1155 to +1160
source: z
.discriminatedUnion("type", [
z.object({ type: z.literal("user"), userId: z.string() }),
z.object({ type: z.literal("integration"), integration: z.string() }),
])
.optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if a changeset has been added for this PR
find .changeset -type f -name '*.md' ! -name 'README.md' 2>/dev/null | head -10

Repository: triggerdotdev/trigger.dev

Length of output: 302


🏁 Script executed:

#!/bin/bash
# Check changeset contents to find which packages are affected
for file in .changeset/*.md; do
  if [ "$(basename "$file")" != "README.md" ]; then
    echo "=== $(basename "$file") ==="
    cat "$file"
    echo ""
  fi
done

Repository: triggerdotdev/trigger.dev

Length of output: 1613


A changeset entry is needed for this packages/core schema modification.

While changesets exist for other changes in this PR, the specific modification to the discriminated union schema in api.ts requires its own changeset. Run pnpm run changeset:add and document this schema change (typically as a patch version for schema additions/modifications).

🤖 Prompt for AI Agents
In `@packages/core/src/v3/schemas/api.ts` around lines 1155 - 1160, Add a
changeset documenting the schema modification to the discriminated union in
packages/core (the source schema defined with z.discriminatedUnion("type", ...)
in packages/core/src/v3/schemas/api.ts); run `pnpm run changeset:add`, select
the packages/core package, write a brief description that this change
adds/modifies the optional "source" discriminated union (user vs integration)
and mark it as a patch release, then commit the generated changeset file with
the PR.

Copy link
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

🤖 Fix all issues with AI agents
In `@apps/webapp/app/models/orgIntegration.server.ts`:
- Around line 99-113: The vercelInstallUrl method interpolates a Base64 `state`
into the query string without URL-encoding, which can corrupt values containing
+, /, =; update static vercelInstallUrl(state: string) to URL-encode the `state`
(e.g., via encodeURIComponent) before building the returned URL (also keep the
existing redirectUri encoding), referencing the vercelInstallUrl method and
env.VERCEL_INTEGRATION_APP_SLUG to locate the change.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cccb4e2 and 13489c6.

📒 Files selected for processing (2)
  • apps/webapp/app/models/orgIntegration.server.ts
  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
🧰 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

**/*.{ts,tsx}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

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

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

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
apps/webapp/app/**/*.{ts,tsx}

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

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

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

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in 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

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.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/alerts/deliverAlert.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/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.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/v3/services/alerts/deliverAlert.server.ts
  • apps/webapp/app/models/orgIntegration.server.ts
🧠 Learnings (3)
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to internal-packages/database/**/*.{ts,tsx} : Use Prisma for database interactions in internal-packages/database with PostgreSQL

Applied to files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/v3/services/alerts/deliverAlert.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/models/orgIntegration.server.ts
🧬 Code graph analysis (1)
apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (1)
apps/webapp/app/models/orgIntegration.server.ts (1)
  • isIntegrationForService (50-55)
⏰ 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: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 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: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/webapp/app/v3/services/alerts/deliverAlert.server.ts (1)

24-28: Good guard for Slack-only integrations.
Using isIntegrationForService prevents a non‑Slack integration from being used when a mismatched integrationId is supplied.

Also applies to: 648-654

apps/webapp/app/models/orgIntegration.server.ts (1)

50-55: Type guard looks solid.
Clear narrowing for service-specific integrations and keeps call sites clean.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
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: 5

Caution

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

⚠️ Outside diff range comments (1)
packages/core/src/v3/schemas/api.ts (1)

680-724: ⚠️ Potential issue | 🟠 Major

Allow integration deployment commitSHA to be nullish to avoid response validation failures.

GetDeploymentResponseBody.commitSHA is already nullish, but integrationDeployments[].commitSHA is required. If an integration deployment lacks a commit (common for some Vercel builds or non‑git sources), the response will fail Zod validation and the API will 500. Consider aligning with the top‑level field.

🔧 Suggested adjustment
-        commitSHA: z.string(),
+        commitSHA: z.string().nullish(),
🤖 Fix all issues with AI agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 330-341: The useEffect that transitions state from
"loading-env-mapping" reads fromMarketplaceContext but doesn't include it in the
dependency array, which can cause stale closures; update the dependency array
for that useEffect to include fromMarketplaceContext (alongside state,
onboardingData, hasStagingEnvironment) so the effect re-runs when
fromMarketplaceContext changes and continue to use setState("env-mapping") /
setState("loading-env-vars") as before.
- Around line 992-994: The prop cast hides a true type mismatch between
onboardingData.gitHubAppInstallations (typed GitHubAppInstallationForVercel from
VercelSettingsPresenter) and the ConnectGitHubRepoModal prop type
(GitHubAppInstallation); fix by unifying the type definitions — either export
and reuse a single shared type (e.g., rename GitHubAppInstallationForVercel to
GitHubAppInstallation and use it in VercelSettingsPresenter and the GitHub
route) or update ConnectGitHubRepoModal to accept the presenter type; then
remove the unsafe `as GitHubAppInstallation[]` cast and update all affected
imports/exports so the types align without assertions.

In `@apps/webapp/app/models/vercelIntegration.server.ts`:
- Around line 68-75: The VercelEnvironmentVariable type is missing the
customEnvironmentIds property which getVercelEnvironmentVariables returns and
the onboarding presenter expects; update the VercelEnvironmentVariable type (the
exported type definition named VercelEnvironmentVariable) to include
customEnvironmentIds?: string[] (or string[] if you want it required) so callers
like getVercelEnvironmentVariables and the onboarding presenter can use it
without excess‑property errors or any casts.

In
`@apps/webapp/app/routes/resources.environments`.$environmentId.regenerate-api-key.tsx:
- Around line 54-65: The background sync ignores non-throwing failures from
VercelIntegrationRepository.syncSingleApiKeyToVercel; update
syncApiKeyToVercelInBackground to inspect the returned result (e.g., const
result = await VercelIntegrationRepository.syncSingleApiKeyToVercel(...)) and if
result.success is false, call your logger (or processLogger) to log a
descriptive error including result.error and context (projectId,
environmentType) so non-exception failures are visible; still keep the try/catch
for thrown errors.

In `@apps/webapp/app/services/referralSource.server.ts`:
- Around line 16-23: The getReferralSource function currently performs a manual
equality check after parsing the cookie; instead validate the parsed value using
the project's Zod schema so unexpected values are rejected. Update logic in
getReferralSource to call referralSourceCookie.parse(cookie) and then validate
the result against the ReferralSource Zod schema (or have
referralSourceCookie.parse already return the Zod-validated value) and only
return the typed ReferralSource when validation succeeds; otherwise return null.
Ensure you reference and use the existing referralSourceCookie and the
ReferralSource schema/type so the function returns a correctly validated
ReferralSource | null.
🧹 Nitpick comments (4)
apps/webapp/app/routes/vercel.onboarding.tsx (1)

283-289: window.close() may silently fail.

The "Close" button calls window.close() which only works if the window was opened programmatically (via window.open). If the user navigated directly to this page, the button will have no effect. Consider adding a fallback redirect.

🔧 Suggested fallback
 <Button
   variant="primary/medium"
-  onClick={() => window.close()}
+  onClick={() => {
+    window.close();
+    // Fallback if window.close() doesn't work
+    setTimeout(() => {
+      window.location.href = "/";
+    }, 100);
+  }}
   className="w-full"
 >
   Close
 </Button>
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (2)

243-246: Magic number in setTimeout delay.

The 100ms delay before redirect seems arbitrary. Consider either removing it if unnecessary, or adding a comment explaining why it's needed (e.g., to ensure state updates complete).

📝 Add explanatory comment
     hasTriggeredMarketplaceRedirectRef.current = true;
+    // Small delay to ensure state updates complete before navigation
     setTimeout(() => {
       window.location.href = nextUrl;
     }, 100);

420-434: Unused dependency in handleSkipOnboarding.

nextUrl is in the dependency array but is not used in the callback body. This suggests either dead code or a missing feature.

🧹 Remove unused dependency
-  }, [actionUrl, fetcher, onClose, nextUrl, fromMarketplaceContext]);
+  }, [actionUrl, fetcher, onClose, fromMarketplaceContext]);
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (1)

937-939: Import at end of file is unconventional.

Moving the import to the top of the file with other imports would follow standard conventions and improve readability.

📝 Move import to top of file

Move line 937 to the imports section at the top of the file (after line 62):

import { VercelOnboardingModal } from "~/components/integrations/VercelOnboardingModal";
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13489c6 and 28f5e4e.

📒 Files selected for processing (19)
  • .changeset/vercel-integration.md
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/auth.github.callback.tsx
  • apps/webapp/app/routes/auth.google.callback.tsx
  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/vercel.callback.ts
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • packages/core/src/v3/schemas/api.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/vercel-integration.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/webapp/app/routes/vercel.callback.ts
  • apps/webapp/app/routes/auth.github.callback.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/auth.google.callback.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{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}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.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/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
apps/webapp/app/**/*.{ts,tsx}

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

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
apps/webapp/**/*.{ts,tsx}

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

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in 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

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.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/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.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/environmentVariables/environmentVariablesRepository.server.ts
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
  • apps/webapp/app/services/referralSource.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/core/src/v3/schemas/api.ts
apps/webapp/app/services/**/*.server.{ts,tsx}

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

Separate testable services from configuration files; follow the pattern of realtimeClient.server.ts (testable service) and realtimeClientGlobal.server.ts (configuration) in the webapp

Files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/services/referralSource.server.ts
🧠 Learnings (29)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/services/referralSource.server.ts
📚 Learning: 2025-09-02T11:18:06.602Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/services/gitHubSession.server.ts:31-36
Timestamp: 2025-09-02T11:18:06.602Z
Learning: In the GitHub App installation flow in apps/webapp/app/services/gitHubSession.server.ts, the redirectTo parameter stored in httpOnly session cookies is considered acceptable without additional validation by the maintainer, as the httpOnly cookie provides sufficient security for this use case.

Applied to files:

  • apps/webapp/app/routes/login.mfa/route.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/login.mfa/route.tsx
  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/magic.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `idempotencyKeys.create()` to create idempotency keys for preventing duplicate task executions

Applied to files:

  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
📚 Learning: 2026-02-03T18:26:53.718Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The webapp at apps/webapp is a Remix 2.1 application using Node.js v20

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-08-19T09:49:07.011Z
Learnt from: julienvanbeveren
Repo: triggerdotdev/trigger.dev PR: 2417
File: apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts:56-61
Timestamp: 2025-08-19T09:49:07.011Z
Learning: In the Trigger.dev codebase, environment variables should default to `isSecret: false` when not explicitly marked as secrets in the syncEnvVars functionality. This is the intended behavior for both regular variables and parent variables.

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/models/vercelIntegration.server.ts
📚 Learning: 2025-04-17T10:27:25.337Z
Learnt from: matt-aitken
Repo: triggerdotdev/trigger.dev PR: 1923
File: packages/core/src/v3/schemas/api.ts:813-827
Timestamp: 2025-04-17T10:27:25.337Z
Learning: Creating secret environment variables is restricted to the dashboard UI only, and not allowed via the API/SDK for now. The `EnvironmentVariableWithSecret` type in the API schema is for reading/displaying purposes only.

Applied to files:

  • apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schedules.task()` for scheduled/cron tasks instead of regular `task()`

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • apps/webapp/app/components/integrations/VercelOnboardingModal.tsx
📚 Learning: 2025-08-14T12:13:20.455Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2392
File: packages/cli-v3/src/utilities/gitMeta.ts:195-218
Timestamp: 2025-08-14T12:13:20.455Z
Learning: In the GitMeta schema (packages/core/src/v3/schemas/common.ts), all fields are intentionally optional to handle partial data from various deployment contexts (local, GitHub Actions, GitHub App). Functions like getGitHubAppMeta() are designed to work with missing environment variables rather than validate their presence.

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`

Applied to files:

  • packages/core/src/v3/schemas/api.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build

Applied to files:

  • packages/core/src/v3/schemas/api.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/services/referralSource.server.ts
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (like the Edit button with PencilSquareIcon) intentionally have no text labels - only icons are shown in the TableCellMenu. This is a deliberate UI design pattern for compact icon-only menu items.

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/services/**/*.server.{ts,tsx} : Separate testable services from configuration files; follow the pattern of `realtimeClient.server.ts` (testable service) and `realtimeClientGlobal.server.ts` (configuration) in the webapp

Applied to files:

  • apps/webapp/app/services/vercelIntegration.server.ts
  • apps/webapp/app/services/referralSource.server.ts
📚 Learning: 2025-06-06T23:55:01.933Z
Learnt from: nicktrn
Repo: triggerdotdev/trigger.dev PR: 2155
File: hosting/docker/.env.example:4-7
Timestamp: 2025-06-06T23:55:01.933Z
Learning: In the trigger.dev project, .env.example files should contain actual example secret values rather than placeholders, as these help users understand the expected format. The files include clear warnings about not using these defaults in production and instructions for generating proper secrets.

Applied to files:

  • apps/webapp/app/models/vercelIntegration.server.ts
🧬 Code graph analysis (9)
apps/webapp/app/routes/vercel.onboarding.tsx (4)
apps/webapp/app/services/session.server.ts (1)
  • requireUserId (25-35)
apps/webapp/app/models/message.server.ts (1)
  • redirectWithErrorMessage (201-218)
apps/webapp/app/utils/pathBuilder.ts (2)
  • confirmBasicDetailsPath (75-77)
  • newProjectPath (137-141)
apps/webapp/app/v3/vercel/vercelOAuthState.server.ts (1)
  • generateVercelOAuthState (15-23)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (1)
apps/webapp/app/v3/environmentVariables/repository.ts (8)
  • CreateEnvironmentVariables (21-28)
  • CreateEnvironmentVariables (30-30)
  • CreateResult (32-40)
  • EditEnvironmentVariable (42-52)
  • EditEnvironmentVariable (53-53)
  • Result (75-82)
  • EditEnvironmentVariableValue (67-72)
  • EditEnvironmentVariableValue (73-73)
apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (6)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (2)
  • VercelOnboardingModal (939-939)
  • vercelResourcePath (415-421)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (6)
  • getAvailableEnvSlugs (96-105)
  • getAvailableEnvSlugsForBuildSettings (107-112)
  • EnvSlug (4-4)
  • SyncEnvVarsMapping (45-45)
  • shouldSyncEnvVarForAnyEnvironment (164-179)
  • ALL_ENV_SLUGS (6-6)
apps/webapp/app/utils/pathBuilder.ts (1)
  • vercelAppInstallPath (162-164)
apps/webapp/app/models/vercelIntegration.server.ts (1)
  • VercelCustomEnvironment (77-85)
apps/webapp/app/components/primitives/Paragraph.tsx (1)
  • Paragraph (88-107)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx (1)
  • GitHubAppInstallation (65-71)
apps/webapp/app/routes/magic.tsx (1)
apps/webapp/app/services/referralSource.server.ts (1)
  • trackAndClearReferralSource (35-52)
apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (6)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (3)
  • loader (140-204)
  • vercelResourcePath (415-421)
  • VercelOnboardingModal (939-939)
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (1)
  • loader (49-116)
apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx (1)
  • loader (38-89)
apps/webapp/app/hooks/useProject.tsx (1)
  • useProject (20-24)
apps/webapp/app/hooks/useEnvironment.tsx (1)
  • useEnvironment (19-23)
apps/webapp/app/utils/pathBuilder.ts (1)
  • vercelResourcePath (170-178)
apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (1)
apps/webapp/app/utils/pathBuilder.ts (4)
  • EnvironmentParamSchema (26-28)
  • v3ProjectSettingsPath (476-482)
  • vercelResourcePath (170-178)
  • vercelAppInstallPath (162-164)
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (3)
packages/core/src/v3/apps/http.ts (1)
  • json (65-75)
apps/webapp/app/services/personalAccessToken.server.ts (1)
  • authenticateApiRequestWithPersonalAccessToken (110-119)
apps/webapp/app/services/vercelIntegration.server.ts (1)
  • VercelIntegrationService (38-599)
apps/webapp/app/services/referralSource.server.ts (1)
apps/webapp/app/services/telemetry.server.ts (1)
  • telemetry (239-245)
apps/webapp/app/models/vercelIntegration.server.ts (2)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (2)
  • projectId (644-684)
  • EnvironmentVariablesRepository (48-864)
apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts (5)
  • envTypeToVercelTarget (80-94)
  • TriggerEnvironmentType (35-35)
  • TriggerEnvironmentType (36-36)
  • SyncEnvVarsMapping (45-45)
  • shouldSyncEnvVar (151-162)
⏰ 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). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 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 (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (51)
apps/webapp/app/v3/environmentVariables/environmentVariablesRepository.server.ts (6)

9-9: LGTM!

The method signatures are now cleaner with structured option types derived from Zod schemas. This aligns with the TypeScript guidelines for using types and Zod for validation.

Also applies to: 13-14, 51-51


159-177: LGTM!

The inheritance logic correctly propagates isSecret: true from parent variables while allowing non-secret status to fall through to the database default. This aligns with the established behavior that variables default to isSecret: false when not explicitly marked as secrets. Based on learnings: "environment variables should default to isSecret: false when not explicitly marked as secrets in the syncEnvVars functionality."


183-200: LGTM!

The idempotent write check is well-designed. It correctly avoids unnecessary database operations when the secret value and metadata are unchanged, while still ensuring updates occur when isSecret status changes or values differ.


214-243: LGTM!

The branched upsert logic with version tracking is well-implemented. The pattern of using undefined for updates (preserve existing) vs Prisma.JsonNull for creates (explicit null) is consistent and correct. The version incrementing provides good audit trail support for the Vercel integration.


273-273: LGTM!

The edit method now properly tracks version changes and updater information. The transaction ensures atomicity between secret store operations and version increments.

Also applies to: 363-376, 394-395


416-416: LGTM!

The editValue method now correctly increments the version and tracks the updater, maintaining consistency with the other mutation methods.

Also applies to: 475-489

apps/webapp/app/routes/login.mfa/route.tsx (2)

29-29: Referral tracking import looks good.


164-170: Referral tracking hook integrates cleanly with the session commits.

apps/webapp/app/services/referralSource.server.ts (3)

1-14: Cookie configuration is sensible and secure.


25-33: Cookie set/clear helpers look good.


35-52: Referral tracking flow is clear and appropriately scoped.

apps/webapp/app/routes/magic.tsx (2)

9-9: Import is appropriate for the new referral tracking hook.


57-57: Referral tracking call placement is correct.

apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (1)

25-132: Auth, membership verification, and CORS handling look solid.

The loader enforces PAT auth, validates params with Zod, checks org membership, and wraps responses with CORS appropriately.

apps/webapp/app/services/vercelIntegration.server.ts (6)

45-125: Integration lookup + parse flow is clear and safe.

Error handling and schema parsing are consistent and return null on invalid data.


127-239: Project selection and sync flow looks good.

The update/create branching plus API‑key sync and logging are well structured.


241-356: Config and env‑var mapping updates are cleanly handled.

The update pattern keeps integrationData consistent and returns parsed data.


385-479: Onboarding flow is robust with sensible logging and fallbacks.

Pull‑sync and TRIGGER_VERSION propagation are handled defensively.


481-582: Production sync guardrails are sensible.

Early returns on missing env/deployment prevent unnecessary API calls.


584-598: Disconnect flow is straightforward and safe.

Soft‑delete keeps history while removing active linkage.

apps/webapp/app/models/vercelIntegration.server.ts (8)

21-47: Helper normalization and error handling utilities are clean.


137-249: OAuth exchange + token validation flow looks solid.


251-367: Configuration + environment variable fetches are well structured.


369-593: Shared env var retrieval and fallback logic is robust.


595-949: Org integration persistence and API‑key sync flows are clear.


951-1232: Pull‑sync pipeline handles filtering, overrides, and logging well.


1234-1417: Batch upsert + single upsert behavior is consistent with Vercel API constraints.


1419-1502: Project settings and uninstall flows are cleanly guarded.

apps/webapp/app/routes/resources.environments.$environmentId.regenerate-api-key.tsx (1)

24-33: Awaiting the sync call is a good reliability improvement.

apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts (3)

18-71: Type surface for settings/onboarding is clear and well scoped.


73-315: Settings aggregation flow is robust with good guardrails.


320-573: Onboarding data assembly is comprehensive and well‑structured.

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.settings/route.tsx (4)

65-105: Loader enhancement for Vercel enablement is clean and minimal.


303-417: Onboarding modal state + fetcher orchestration looks solid.


547-560: Settings panel wiring is straightforward and well‑gated.


615-635: Onboarding modal integration is neatly hooked up.

apps/webapp/app/routes/vercel.onboarding.tsx (5)

1-24: Imports look good.

The imports are well-organized and follow project conventions. Using Zod for validation and typedjson for typed responses aligns with coding guidelines.


25-53: Well-structured Zod schemas using discriminated union pattern.

The schema design correctly uses z.discriminatedUnion for type-safe action handling. The validation is thorough.


55-164: Loader implementation is solid.

Good patterns:

  • Authentication via requireUserId
  • Zod validation with proper error handling
  • Authorization check via membership filtering
  • Proper redirect handling for edge cases (no orgs, invalid params)

270-275: Loading state reset is properly implemented.

The useEffect correctly resets isInstalling when navigation returns to idle, addressing the previous review concern about stuck loading states.


224-235: Hardcoded "prod" environment slug is intentional for Vercel integration.

The environment lookup uses slug: "prod" consistently across the Vercel integration flow. This is documented in the Vercel settings file with a comment ("Default to production environment") and follows the same pattern in project creation (_app.orgs.$organizationSlug_.projects.new/route.tsx). The design limits Vercel integrations to the production environment by default, which is the intended behavior.

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx (3)

1-65: Imports and helper function look good.

The formatVercelTargets helper provides clean target label formatting. Imports are well-organized.


66-78: Clean state machine type definition.

Using a string union type for the state machine is appropriate and follows the coding guideline of preferring types over interfaces.


79-141: Component setup is well-structured.

Props are clearly defined, initial state computation is properly memoized with useCallback, and the marketplace context check is appropriately derived from search params.

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx (7)

1-72: Imports and type definition look good.

The ConnectedVercelProject type properly uses type instead of interface per coding guidelines. Imports are well-organized.


73-139: Schemas and helper function are well-implemented.

The parseVercelStagingEnvironment helper safely handles JSON parsing with proper error handling. The discriminated union pattern for action routing is appropriate.


140-204: Loader implementation is solid.

Good patterns:

  • Proper authentication and authorization
  • Conditional data fetching for onboarding
  • Error handling distinguishes Response errors from unexpected errors
  • Logging includes useful context

305-315: Good security practice on redirect URL validation.

The https-only check for the next URL parameter prevents open redirect vulnerabilities to non-secure destinations. The try-catch handles malformed URLs gracefully.


411-412: Excellent exhaustiveness check.

Using submission.value satisfies never ensures TypeScript will error if a new action type is added to the schema but not handled in the action function.


762-762: Path now correctly includes environment slug.

The envVarsConfigLink properly includes all path segments including environmentSlug, addressing the previous review feedback.


415-421: Do not apply this refactor — the vercelResourcePath functions in pathBuilder.ts and this file have incompatible signatures. The one in pathBuilder.ts accepts typed objects (OrgForPath, ProjectForPath, EnvironmentForPath) and uses helper functions like organizationParam(), while this function accepts plain strings. They are not duplicates and cannot be interchanged.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +330 to +341
// For marketplace origin, skip env-mapping step
useEffect(() => {
if (state === "loading-env-mapping" && onboardingData) {
const hasCustomEnvs = (onboardingData.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment;
if (hasCustomEnvs && !fromMarketplaceContext) {
setState("env-mapping");
} else {
setState("loading-env-vars");
}
}
}, [state, onboardingData, hasStagingEnvironment]);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing dependency in useEffect.

fromMarketplaceContext is used in the condition at line 334 but is not included in the dependency array at line 340. This could cause stale closure issues.

🐛 Add missing dependency
   useEffect(() => {
     if (state === "loading-env-mapping" && onboardingData) {
       const hasCustomEnvs = (onboardingData.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment;
       if (hasCustomEnvs && !fromMarketplaceContext) {
         setState("env-mapping");
       } else {
         setState("loading-env-vars");
       }
     }
-  }, [state, onboardingData, hasStagingEnvironment]);
+  }, [state, onboardingData, hasStagingEnvironment, fromMarketplaceContext]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// For marketplace origin, skip env-mapping step
useEffect(() => {
if (state === "loading-env-mapping" && onboardingData) {
const hasCustomEnvs = (onboardingData.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment;
if (hasCustomEnvs && !fromMarketplaceContext) {
setState("env-mapping");
} else {
setState("loading-env-vars");
}
}
}, [state, onboardingData, hasStagingEnvironment]);
// For marketplace origin, skip env-mapping step
useEffect(() => {
if (state === "loading-env-mapping" && onboardingData) {
const hasCustomEnvs = (onboardingData.customEnvironments?.length ?? 0) > 0 && hasStagingEnvironment;
if (hasCustomEnvs && !fromMarketplaceContext) {
setState("env-mapping");
} else {
setState("loading-env-vars");
}
}
}, [state, onboardingData, hasStagingEnvironment, fromMarketplaceContext]);
🤖 Prompt for AI Agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx` around
lines 330 - 341, The useEffect that transitions state from "loading-env-mapping"
reads fromMarketplaceContext but doesn't include it in the dependency array,
which can cause stale closures; update the dependency array for that useEffect
to include fromMarketplaceContext (alongside state, onboardingData,
hasStagingEnvironment) so the effect re-runs when fromMarketplaceContext changes
and continue to use setState("env-mapping") / setState("loading-env-vars") as
before.

Comment on lines +992 to +994
<ConnectGitHubRepoModal
gitHubAppInstallations={gitHubAppInstallations as GitHubAppInstallation[]}
organizationSlug={organizationSlug}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n apps/webapp/app/components/integrations/VercelOnboardingModal.tsx | sed -n '980,1010p'

Repository: triggerdotdev/trigger.dev

Length of output: 1707


🏁 Script executed:

# Search for the onboardingData type definition
rg "onboardingData.*:" apps/webapp/app/components/integrations/VercelOnboardingModal.tsx -A 5 | head -30

Repository: triggerdotdev/trigger.dev

Length of output: 261


🏁 Script executed:

# Find GitHubAppInstallation type definition
fd -e ts -e tsx | xargs rg "type GitHubAppInstallation\s*=" | head -5

Repository: triggerdotdev/trigger.dev

Length of output: 210


🏁 Script executed:

# Find ConnectGitHubRepoModal component and its props
rg "export.*ConnectGitHubRepoModal|interface.*ConnectGitHubRepoModal.*Props|type.*ConnectGitHubRepoModal.*Props" -A 10

Repository: triggerdotdev/trigger.dev

Length of output: 1473


🏁 Script executed:

# Check the complete context around line 993
cat -n apps/webapp/app/components/integrations/VercelOnboardingModal.tsx | sed -n '985,1000p'

Repository: triggerdotdev/trigger.dev

Length of output: 995


🏁 Script executed:

# Find VercelOnboardingData type definition
rg "type VercelOnboardingData\s*=" -A 20

Repository: triggerdotdev/trigger.dev

Length of output: 2215


🏁 Script executed:

# Search for where gitHubAppInstallations is destructured in VercelOnboardingModal
rg "const.*gitHubAppInstallations|destructuring.*gitHubAppInstallations|gitHubAppInstallations\s*=" apps/webapp/app/components/integrations/VercelOnboardingModal.tsx -B 3 -A 2

Repository: triggerdotdev/trigger.dev

Length of output: 778


🏁 Script executed:

# Check the beginning of the VercelOnboardingModal component to see variable declarations
cat -n apps/webapp/app/components/integrations/VercelOnboardingModal.tsx | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 4157


🏁 Script executed:

# Find GitHubAppInstallationForVercel type definition
rg "type GitHubAppInstallationForVercel\s*=" -A 15

Repository: triggerdotdev/trigger.dev

Length of output: 1448


🏁 Script executed:

# Find GitHubAppInstallation type definition (from the route file)
rg "export type GitHubAppInstallation\s*=" apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.github.tsx -A 15

Repository: triggerdotdev/trigger.dev

Length of output: 173


🏁 Script executed:

# Find the correct file path
fd "github.tsx" apps/webapp/app/routes/

Repository: triggerdotdev/trigger.dev

Length of output: 172


🏁 Script executed:

# Search for GitHubAppInstallation type in the codebase
rg "export type GitHubAppInstallation\s*=" -A 15

Repository: triggerdotdev/trigger.dev

Length of output: 2161


Unify type definitions to eliminate unnecessary type assertion.

The cast as GitHubAppInstallation[] at line 993 masks a real type mismatch: onboardingData.gitHubAppInstallations is typed as GitHubAppInstallationForVercel[] (from VercelSettingsPresenter), but ConnectGitHubRepoModal expects GitHubAppInstallation[]. Both types have identical structure—consider using a single type definition across both the presenter and the GitHub route to eliminate this unsafe cast.

🤖 Prompt for AI Agents
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx` around
lines 992 - 994, The prop cast hides a true type mismatch between
onboardingData.gitHubAppInstallations (typed GitHubAppInstallationForVercel from
VercelSettingsPresenter) and the ConnectGitHubRepoModal prop type
(GitHubAppInstallation); fix by unifying the type definitions — either export
and reuse a single shared type (e.g., rename GitHubAppInstallationForVercel to
GitHubAppInstallation and use it in VercelSettingsPresenter and the GitHub
route) or update ConnectGitHubRepoModal to accept the presenter type; then
remove the unsafe `as GitHubAppInstallation[]` cast and update all affected
imports/exports so the types align without assertions.

Comment on lines +68 to +75
export type VercelEnvironmentVariable = {
id: string;
key: string;
type: "system" | "encrypted" | "plain" | "sensitive" | "secret";
isSecret: boolean;
target: string[];
isShared?: boolean;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Type mismatch: add customEnvironmentIds to VercelEnvironmentVariable.

getVercelEnvironmentVariables() returns customEnvironmentIds, and the onboarding presenter relies on it. Without typing it, you either hit an excess‑property error or need any casts.

🔧 Suggested fix
 export type VercelEnvironmentVariable = {
   id: string;
   key: string;
   type: "system" | "encrypted" | "plain" | "sensitive" | "secret";
   isSecret: boolean;
   target: string[];
+  customEnvironmentIds?: string[];
   isShared?: boolean;
 };
🤖 Prompt for AI Agents
In `@apps/webapp/app/models/vercelIntegration.server.ts` around lines 68 - 75, The
VercelEnvironmentVariable type is missing the customEnvironmentIds property
which getVercelEnvironmentVariables returns and the onboarding presenter
expects; update the VercelEnvironmentVariable type (the exported type definition
named VercelEnvironmentVariable) to include customEnvironmentIds?: string[] (or
string[] if you want it required) so callers like getVercelEnvironmentVariables
and the onboarding presenter can use it without excess‑property errors or any
casts.

Comment on lines +54 to +65
async function syncApiKeyToVercelInBackground(
projectId: string,
environmentType: "PRODUCTION" | "STAGING" | "PREVIEW" | "DEVELOPMENT",
apiKey: string
): Promise<void> {
try {
const result = await VercelIntegrationRepository.syncSingleApiKeyToVercel({
projectId,
environmentType,
apiKey,
});
} catch (error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Log non-throwing failures from syncSingleApiKeyToVercel.

The repository method returns { success: false, error } without throwing. Right now that path is silently ignored.

🔧 Suggested fix
   try {
     const result = await VercelIntegrationRepository.syncSingleApiKeyToVercel({
       projectId,
       environmentType,
       apiKey,
     });
+    if (!result.success) {
+      logger.warn("Failed to sync regenerated API key to Vercel", {
+        projectId,
+        environmentType,
+        error: result.error,
+      });
+    }
   } catch (error) {
🤖 Prompt for AI Agents
In
`@apps/webapp/app/routes/resources.environments`.$environmentId.regenerate-api-key.tsx
around lines 54 - 65, The background sync ignores non-throwing failures from
VercelIntegrationRepository.syncSingleApiKeyToVercel; update
syncApiKeyToVercelInBackground to inspect the returned result (e.g., const
result = await VercelIntegrationRepository.syncSingleApiKeyToVercel(...)) and if
result.success is false, call your logger (or processLogger) to log a
descriptive error including result.error and context (projectId,
environmentType) so non-exception failures are visible; still keep the try/catch
for thrown errors.

Comment on lines +16 to +23
export async function getReferralSource(request: Request): Promise<ReferralSource | null> {
const cookie = request.headers.get("Cookie");
const value = await referralSourceCookie.parse(cookie);
if (value === "vercel") {
return value;
}
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read its content
cat -n apps/webapp/app/services/referralSource.server.ts

Repository: triggerdotdev/trigger.dev

Length of output: 2004


🏁 Script executed:

# Check if zod is already in the project dependencies
cat package.json | grep -A 10 -B 2 "zod"

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Search for other Zod validation patterns in apps/webapp to understand the convention
rg "z\.enum|z\.string|safeParse" apps/webapp --type ts --type tsx -A 2 | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 96


🏁 Script executed:

# Search for zod in the project dependencies (broader search)
rg "\"zod\"" --type json | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 1259


🏁 Script executed:

# Search for Zod validation patterns in apps/webapp to understand the convention
rg "z\.(enum|string|object|safeParse)" apps/webapp -A 2 | head -80

Repository: triggerdotdev/trigger.dev

Length of output: 5045


🏁 Script executed:

# Check if zod is in apps/webapp's dependencies
cat apps/webapp/package.json | grep -A 5 -B 5 "zod"

Repository: triggerdotdev/trigger.dev

Length of output: 1256


Use Zod to validate the referral source cookie value.
Manual checks bypass the project's validation convention.

♻️ Proposed refactor
-import { createCookie } from "@remix-run/node";
+import { createCookie } from "@remix-run/node";
+import { z } from "zod";
@@
-export type ReferralSource = "vercel";
+const ReferralSourceSchema = z.enum(["vercel"]);
+export type ReferralSource = z.infer<typeof ReferralSourceSchema>;
@@
 export async function getReferralSource(request: Request): Promise<ReferralSource | null> {
   const cookie = request.headers.get("Cookie");
   const value = await referralSourceCookie.parse(cookie);
-  if (value === "vercel") {
-    return value;
-  }
-  return null;
+  const parsed = ReferralSourceSchema.safeParse(value);
+  return parsed.success ? parsed.data : null;
 }
🤖 Prompt for AI Agents
In `@apps/webapp/app/services/referralSource.server.ts` around lines 16 - 23, The
getReferralSource function currently performs a manual equality check after
parsing the cookie; instead validate the parsed value using the project's Zod
schema so unexpected values are rejected. Update logic in getReferralSource to
call referralSourceCookie.parse(cookie) and then validate the result against the
ReferralSource Zod schema (or have referralSourceCookie.parse already return the
Zod-validated value) and only return the typed ReferralSource when validation
succeeds; otherwise return null. Ensure you reference and use the existing
referralSourceCookie and the ReferralSource schema/type so the function returns
a correctly validated ReferralSource | null.

Copy link
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: 4

🤖 Fix all issues with AI agents
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.integrations.vercel.tsx:
- Around line 46-55: The loader is parsing configurationId from route params but
the value comes from the query string; update the loader to read configurationId
from the URL's search params (use the existing url variable and
url.searchParams.get("configurationId")) and pass that value into
SearchParamsSchema.parse (e.g., build an input object merging params with
configurationId from url.searchParams.get) before using it to filter
integrations; keep references to SearchParamsSchema, loader, configurationId and
requireOrganization so the change is local to that function.

In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx:
- Around line 553-559: The GitHub warning text in VercelGitHubWarning and the
corresponding hint (both in this file) incorrectly state that the Vercel
integration "spawns Trigger.dev builds"; update the copy to reflect the correct
scope: Vercel only links deployments and does not trigger builds or provide git
metadata (the GitHub app does that). Locate the VercelGitHubWarning component
and the hint text referenced near the other occurrence and replace phrases like
"spawns Trigger.dev builds" and any mention of providing git metadata with
wording such as "links deployments; builds and git metadata are provided by the
GitHub app" (or equivalent concise copy conveying that separation of
responsibilities).

In `@apps/webapp/app/routes/vercel.connect.tsx`:
- Around line 13-27: Update the VercelConnectSchema and the
createOrFindVercelIntegration signature to accept an optional configurationId:
make configurationId optional in VercelConnectSchema (change z.string() to an
optional string/nullable) and update the createOrFindVercelIntegration parameter
type for configurationId to be optional (e.g., configurationId?: string or
string | null) so dashboard flows that omit configurationId pass validation;
ensure callers (e.g., vercel.callback.ts) pass undefined/null when no
configurationId is present and downstream uses (installationId/integrationData)
can handle null/undefined.

In `@apps/webapp/app/routes/vercel.onboarding.tsx`:
- Around line 26-85: The loader validation fails for the expired path because
LoaderParamsSchema requires code; change code to optional (e.g., code:
z.string().optional().nullable()) in LoaderParamsSchema so the loader can accept
?error=expired without a code, and update the loader usages (in export async
function loader) to treat params.data.code as possibly null/undefined (e.g., use
params.data.code ?? null when returning typedjson) so the "Installation Expired"
UI can render.
🧹 Nitpick comments (1)
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (1)

110-120: Consider including triggerProject in the not-connected response for API consistency.

The project is already available at line 108 when no integration exists. Including triggerProject in this response would provide a consistent shape regardless of connection status, which simplifies client-side type handling.

♻️ Suggested change for consistent response shape
   if (!integration) {
     return apiCors(
       request,
       json({
         connected: false,
         vercelProject: null,
         config: null,
         syncEnvVarsMapping: null,
+        triggerProject: {
+          id: project.id,
+          name: project.name,
+          slug: project.slug,
+        },
       })
     );
   }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28f5e4e and 29a3434.

📒 Files selected for processing (5)
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{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}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
{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/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
**/*.{ts,tsx,js,jsx}

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

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
apps/webapp/app/**/*.{ts,tsx}

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

Access all environment variables through the env export of env.server.ts instead of directly accessing process.env in the Trigger.dev webapp

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
apps/webapp/**/*.{ts,tsx}

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

apps/webapp/**/*.{ts,tsx}: When importing from @trigger.dev/core in 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

Access environment variables via env export from apps/webapp/app/env.server.ts, never use process.env directly

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
**/*.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/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
🧠 Learnings (15)
📓 Common learnings
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-12-08T15:19:56.823Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2760
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:278-281
Timestamp: 2025-12-08T15:19:56.823Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx, the tableState search parameter uses intentional double-encoding: the parameter value contains a URL-encoded URLSearchParams string, so decodeURIComponent(value("tableState") ?? "") is required to fully decode it before parsing with new URLSearchParams(). This pattern allows bundling multiple filter/pagination params as a single search parameter.

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/services/**/*.server.{ts,tsx} : Organize services in the webapp following the pattern `app/v3/services/*/*.server.ts`

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/v3/presenters/**/*.server.{ts,tsx} : Organize presenters in the webapp following the pattern `app/v3/presenters/*/*.server.ts` to move complex loader code into classes

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
📚 Learning: 2026-02-03T18:27:40.429Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx:553-555
Timestamp: 2026-02-03T18:27:40.429Z
Learning: In apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx, the menu buttons (e.g., Edit with PencilSquareIcon) in the TableCellMenu are intentionally icon-only with no text labels as a compact UI pattern. This is a deliberate design choice for this route; preserve the icon-only behavior for consistency in this file.

Applied to files:

  • apps/webapp/app/routes/vercel.connect.tsx
  • apps/webapp/app/routes/vercel.onboarding.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The webapp at apps/webapp is a Remix 2.1 application using Node.js v20

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
📚 Learning: 2025-09-02T11:27:36.336Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2463
File: apps/webapp/app/routes/_app.github.callback/route.tsx:33-44
Timestamp: 2025-09-02T11:27:36.336Z
Learning: In the GitHub App installation callback flow in apps/webapp/app/routes/_app.github.callback/route.tsx, the install session cookie is not cleared after use due to interface limitations with redirectWithSuccessMessage/redirectWithErrorMessage not supporting custom headers. The maintainer accepts this design as the 1-hour cookie expiration provides sufficient protection against replay attacks.

Applied to files:

  • apps/webapp/app/routes/vercel.onboarding.tsx
📚 Learning: 2026-02-03T18:26:53.718Z
Learnt from: 0ski
Repo: triggerdotdev/trigger.dev PR: 2994
File: apps/webapp/app/presenters/v3/BranchesPresenter.server.ts:45-45
Timestamp: 2026-02-03T18:26:53.718Z
Learning: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : Use zod for validation in packages/core and apps/webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
📚 Learning: 2025-09-03T14:35:52.384Z
Learnt from: myftija
Repo: triggerdotdev/trigger.dev PR: 2464
File: apps/webapp/app/utils/pathBuilder.ts:144-146
Timestamp: 2025-09-03T14:35:52.384Z
Learning: In the trigger.dev codebase, organization slugs are safe for URL query parameters and don't require URL encoding, as confirmed by the maintainer in apps/webapp/app/utils/pathBuilder.ts.

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/app/**/*.{ts,tsx} : Access all environment variables through the `env` export of `env.server.ts` instead of directly accessing `process.env` in the Trigger.dev webapp

Applied to files:

  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Access environment variables via `env` export from `apps/webapp/app/env.server.ts`, never use `process.env` directly

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path

Applied to files:

  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
🧬 Code graph analysis (2)
apps/webapp/app/routes/vercel.connect.tsx (5)
apps/webapp/app/models/vercelIntegration.server.ts (1)
  • TokenResponse (60-66)
apps/webapp/app/routes/vercel.callback.ts (1)
  • loader (20-74)
apps/webapp/app/services/session.server.ts (1)
  • requireUserId (25-35)
apps/webapp/app/v3/vercel/vercelOAuthState.server.ts (1)
  • validateVercelOAuthState (25-40)
apps/webapp/app/utils/pathBuilder.ts (1)
  • v3ProjectSettingsPath (476-482)
apps/webapp/app/routes/_app.orgs.$organizationSlug.settings.integrations.vercel.tsx (1)
apps/webapp/app/utils/pathBuilder.ts (2)
  • OrganizationParamsSchema (18-20)
  • v3ProjectSettingsPath (476-482)
⏰ 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 (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: typecheck / typecheck
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts (2)

32-39: Authentication and authorization properly implemented.

The endpoint now correctly authenticates requests using authenticateApiRequestWithPersonalAccessToken before any data access, and the Prisma query at lines 59-63 verifies organization membership via members: { some: { userId: authenticationResult.userId } }. This properly addresses the previously flagged security concern.


51-86: Clean neverthrow pattern with efficient org membership verification.

The fromPromise wrapper and discriminated union return type (type: "not_found" | "success") provide clear control flow. Embedding the membership check in the Prisma query (lines 59-63) is efficient—single query validates access and retrieves data.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +46 to +55
const SearchParamsSchema = OrganizationParamsSchema.extend({
configurationId: z.string().optional(),
});

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const { organizationSlug, configurationId } = SearchParamsSchema.parse(params);
const { organization } = await requireOrganization(request, organizationSlug);

const url = new URL(request.url);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

configurationId query param is ignored in the loader.

You parse configurationId from params, but it only exists in the query string, so the filter never applies. Use url.searchParams.get("configurationId") when building the parse input.

🔧 Suggested fix
-export const loader = async ({ request, params }: LoaderFunctionArgs) => {
-  const { organizationSlug, configurationId } = SearchParamsSchema.parse(params);
-  const { organization } = await requireOrganization(request, organizationSlug);
-  
-  const url = new URL(request.url);
+export const loader = async ({ request, params }: LoaderFunctionArgs) => {
+  const url = new URL(request.url);
+  const { organizationSlug, configurationId } = SearchParamsSchema.parse({
+    organizationSlug: params.organizationSlug,
+    configurationId: url.searchParams.get("configurationId"),
+  });
+  const { organization } = await requireOrganization(request, organizationSlug);
🤖 Prompt for AI Agents
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.settings.integrations.vercel.tsx
around lines 46 - 55, The loader is parsing configurationId from route params
but the value comes from the query string; update the loader to read
configurationId from the URL's search params (use the existing url variable and
url.searchParams.get("configurationId")) and pass that value into
SearchParamsSchema.parse (e.g., build an input object merging params with
configurationId from url.searchParams.get) before using it to filter
integrations; keep references to SearchParamsSchema, loader, configurationId and
requireOrganization so the change is local to that function.

Comment on lines +553 to +559
function VercelGitHubWarning() {
return (
<Callout variant="warning" className="mb-4">
<p className="font-sans text-xs font-normal text-text-dimmed">
GitHub integration is not connected. Vercel integration cannot pull environment variables or
spawn Trigger.dev builds without a properly installed GitHub integration.
</p>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update GitHub warning copy to match Vercel integration scope.

Both the warning and hint text imply Vercel integration “spawns Trigger.dev builds,” which conflicts with the integration’s role (it links deployments; GitHub app owns builds/metadata). Please adjust the copy in both places to avoid misleading users.

✏️ Suggested copy updates
-        GitHub integration is not connected. Vercel integration cannot pull environment variables or
-        spawn Trigger.dev builds without a properly installed GitHub integration.
+        GitHub integration is not connected. Vercel integration links deployments, but build
+        metadata comes from the GitHub app. Please install/enable GitHub integration to sync
+        metadata and environment variables.
-              ? "Connect your Vercel project to pull environment variables and trigger builds automatically."
+              ? "Connect your Vercel project to link deployments and sync environment variables."
               : "Install the Vercel app to connect your projects and pull environment variables."}
-              GitHub integration is not connected. Vercel integration cannot pull environment variables or
-              spawn Trigger.dev builds without a properly installed GitHub integration.
+              GitHub integration is not connected. Build metadata comes from the GitHub app, so
+              link deployments after installing the GitHub integration.

Based on learnings: In the Vercel integration feature, the GitHub app is responsible for builds and provides git metadata (using source: "trigger_github_app"). The Vercel integration is only for linking deployments between platforms, not for triggering builds or providing git metadata.

Also applies to: 944-951

🤖 Prompt for AI Agents
In
`@apps/webapp/app/routes/resources.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx
around lines 553 - 559, The GitHub warning text in VercelGitHubWarning and the
corresponding hint (both in this file) incorrectly state that the Vercel
integration "spawns Trigger.dev builds"; update the copy to reflect the correct
scope: Vercel only links deployments and does not trigger builds or provide git
metadata (the GitHub app does that). Locate the VercelGitHubWarning component
and the hint text referenced near the other occurrence and replace phrases like
"spawns Trigger.dev builds" and any mention of providing git metadata with
wording such as "links deployments; builds and git metadata are provided by the
GitHub app" (or equivalent concise copy conveying that separation of
responsibilities).

Comment on lines +13 to +27
const VercelConnectSchema = z.object({
state: z.string(),
configurationId: z.string(),
code: z.string(),
next: z.string().optional(),
origin: z.enum(["marketplace", "dashboard"]),
});

async function createOrFindVercelIntegration(
organizationId: string,
projectId: string,
tokenResponse: TokenResponse,
configurationId: string,
origin: 'marketplace' | 'dashboard'
): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the vercel.connect.tsx file
fd "vercel\.connect\.tsx" apps/webapp --type f

Repository: triggerdotdev/trigger.dev

Length of output: 110


🏁 Script executed:

# Also search for the vercel.callback file to understand the flow
fd "vercel\.callback\.tsx" apps/webapp --type f

Repository: triggerdotdev/trigger.dev

Length of output: 51


🏁 Script executed:

# Let's also check the VercelIntegrationRepository to understand what fields it expects
fd -e "ts" -e "tsx" | xargs rg "VercelIntegrationRepository" --type-list -l | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 458


🏁 Script executed:

# Read the vercel.connect.tsx file
cat -n apps/webapp/app/routes/vercel.connect.tsx

Repository: triggerdotdev/trigger.dev

Length of output: 6475


🏁 Script executed:

# Search for vercel callback route with different patterns
fd "vercel" apps/webapp --type f | grep -i callback

Repository: triggerdotdev/trigger.dev

Length of output: 110


🏁 Script executed:

# Search for VercelIntegrationRepository
rg "VercelIntegrationRepository" apps/webapp --type ts --type tsx -l

Repository: triggerdotdev/trigger.dev

Length of output: 96


🏁 Script executed:

# Read the vercel.callback.ts file
cat -n apps/webapp/app/routes/vercel.callback.ts

Repository: triggerdotdev/trigger.dev

Length of output: 3374


🏁 Script executed:

# Find VercelIntegrationRepository definition
rg "VercelIntegrationRepository" apps/webapp -l

Repository: triggerdotdev/trigger.dev

Length of output: 550


🏁 Script executed:

# Check the VercelIntegrationRepository to understand what fields it expects
cat -n apps/webapp/app/models/vercelIntegration.server.ts | head -200

Repository: triggerdotdev/trigger.dev

Length of output: 7181


🏁 Script executed:

# Continue reading the VercelIntegrationRepository to see the create and update methods
cat -n apps/webapp/app/models/vercelIntegration.server.ts | sed -n '200,400p'

Repository: triggerdotdev/trigger.dev

Length of output: 6746


🏁 Script executed:

# Search for the createVercelOrgIntegration and updateVercelOrgIntegrationToken methods
rg "createVercelOrgIntegration|updateVercelOrgIntegrationToken" apps/webapp/app/models/vercelIntegration.server.ts -A 15

Repository: triggerdotdev/trigger.dev

Length of output: 1142


🏁 Script executed:

# Let's verify the database schema to understand if installationId can actually be null
rg "installationId" apps/webapp -B 2 -A 2 --type ts | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 4553


🏁 Script executed:

# Check the prisma schema to see if installationId can be null
fd "schema.prisma" --type f | xargs grep -A 5 "OrganizationIntegration"

Repository: triggerdotdev/trigger.dev

Length of output: 2639


🏁 Script executed:

# Get the full OrganizationIntegration model definition
sed -n '/^model OrganizationIntegration/,/^}/p' internal-packages/database/prisma/schema.prisma

Repository: triggerdotdev/trigger.dev

Length of output: 901


🏁 Script executed:

# Check if there are any uses or tests that show dashboard flow without configurationId
rg "origin.*dashboard" apps/webapp --type ts -B 3 -A 3 | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 2339


🏁 Script executed:

# Also check if there's any code that handles the configurationId being undefined
rg "configurationId" apps/webapp/app/routes/vercel.connect.tsx -B 2 -A 2

Repository: triggerdotdev/trigger.dev

Length of output: 1036


Make configurationId optional to support dashboard flows without it.

The dashboard flow in vercel.callback.ts conditionally adds configurationId only when present (line 60). However, VercelConnectSchema currently requires it, causing valid dashboard flows without a configuration ID to fail with a 400 error during schema validation. The downstream repository methods already support optional installationId, and the integrationData field can store null values.

🔧 Suggested change
 const VercelConnectSchema = z.object({
   state: z.string(),
-  configurationId: z.string(),
+  configurationId: z.string().optional(),
   code: z.string(),
   next: z.string().optional(),
   origin: z.enum(["marketplace", "dashboard"]),
 });

 async function createOrFindVercelIntegration(
   organizationId: string,
   projectId: string,
   tokenResponse: TokenResponse,
-  configurationId: string,
+  configurationId?: string,
   origin: 'marketplace' | 'dashboard'
 ): Promise<void> {
   ...
     await VercelIntegrationRepository.updateVercelOrgIntegrationToken({
       ...
-      installationId: configurationId,
+      installationId: configurationId ?? null,
       ...
     });
   } else {
     await VercelIntegrationRepository.createVercelOrgIntegration({
       ...
-      installationId: configurationId,
+      installationId: configurationId ?? null,
       ...
     });
   }
 }
🤖 Prompt for AI Agents
In `@apps/webapp/app/routes/vercel.connect.tsx` around lines 13 - 27, Update the
VercelConnectSchema and the createOrFindVercelIntegration signature to accept an
optional configurationId: make configurationId optional in VercelConnectSchema
(change z.string() to an optional string/nullable) and update the
createOrFindVercelIntegration parameter type for configurationId to be optional
(e.g., configurationId?: string or string | null) so dashboard flows that omit
configurationId pass validation; ensure callers (e.g., vercel.callback.ts) pass
undefined/null when no configurationId is present and downstream uses
(installationId/integrationData) can handle null/undefined.

Comment on lines +26 to +85
const LoaderParamsSchema = z.object({
organizationId: z.string().optional().nullable(),
code: z.string(),
configurationId: z.string().optional().nullable(),
next: z.string().optional().nullable(),
error: z.string().optional().nullable(),
});

const SelectOrgActionSchema = z.object({
action: z.literal("select-org"),
organizationId: z.string(),
code: z.string(),
configurationId: z.string().optional().nullable(),
next: z.string().optional(),
});

const SelectProjectActionSchema = z.object({
action: z.literal("select-project"),
projectId: z.string(),
organizationId: z.string(),
code: z.string(),
configurationId: z.string().optional().nullable(),
next: z.string().optional().nullable(),
});

const ActionSchema = z.discriminatedUnion("action", [
SelectOrgActionSchema,
SelectProjectActionSchema,
]);

export async function loader({ request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const url = new URL(request.url);

const params = LoaderParamsSchema.safeParse({
organizationId: url.searchParams.get("organizationId"),
code: url.searchParams.get("code"),
configurationId: url.searchParams.get("configurationId"),
next: url.searchParams.get("next"),
error: url.searchParams.get("error"),
});

if (!params.success) {
logger.error("Invalid params for Vercel onboarding", { error: params.error });
throw redirectWithErrorMessage(
"/",
request,
"Invalid installation parameters. Please try again from Vercel."
);
}

const { error } = params.data;
if (error === "expired") {
return typedjson({
step: "error" as const,
error: "Your installation session has expired. Please start the installation again.",
code: params.data.code,
configurationId: params.data.configurationId ?? null,
next: params.data.next ?? null,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find the file and check its current state
find . -name "vercel.onboarding.tsx" -type f

Repository: triggerdotdev/trigger.dev

Length of output: 115


🏁 Script executed:

# Search for the vercel connect route/endpoint
find . -name "*vercel*" -name "*.tsx" -o -name "*.ts" | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 841


🏁 Script executed:

# Check the vercel.onboarding.tsx file
cat -n ./apps/webapp/app/routes/vercel.onboarding.tsx | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 4416


🏁 Script executed:

# Search for vercel.connect route
find ./apps/webapp/app/routes -name "*vercel*" -type f

Repository: triggerdotdev/trigger.dev

Length of output: 582


🏁 Script executed:

# Check vercel.connect.tsx to see what it redirects with
cat -n ./apps/webapp/app/routes/vercel.connect.tsx

Repository: triggerdotdev/trigger.dev

Length of output: 6475


Expired installation path can't render because code is required.

/vercel/connect redirects with ?error=expired and no code (lines 86–87, 121–122 in vercel.connect.tsx), but the loader schema requires code, so it fails validation before returning the "Installation Expired" UI. Make code optional when error=expired, or validate error before requiring code.

🩹 Suggested fix
-const LoaderParamsSchema = z.object({
+const LoaderParamsSchema = z.object({
   organizationId: z.string().optional().nullable(),
-  code: z.string(),
+  code: z.string().optional(),
   configurationId: z.string().optional().nullable(),
   next: z.string().optional().nullable(),
   error: z.string().optional().nullable(),
-});
+}).superRefine((data, ctx) => {
+  if (data.error !== "expired" && !data.code) {
+    ctx.addIssue({
+      code: z.ZodIssueCode.custom,
+      path: ["code"],
+      message: "code is required",
+    });
+  }
+});
🤖 Prompt for AI Agents
In `@apps/webapp/app/routes/vercel.onboarding.tsx` around lines 26 - 85, The
loader validation fails for the expired path because LoaderParamsSchema requires
code; change code to optional (e.g., code: z.string().optional().nullable()) in
LoaderParamsSchema so the loader can accept ?error=expired without a code, and
update the loader usages (in export async function loader) to treat
params.data.code as possibly null/undefined (e.g., use params.data.code ?? null
when returning typedjson) so the "Installation Expired" UI can render.

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